Compare commits
1998 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
dd2a698147 | |||
cc65555c3d | |||
63e3923349 |
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?
|
11
.gitignore
vendored
11
.gitignore
vendored
@ -22,10 +22,17 @@ Oqtane.Server/Packages
|
||||
Oqtane.Server/wwwroot/Content
|
||||
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/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/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!
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
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
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,91 +0,0 @@
|
||||
@using Microsoft.AspNetCore.Http
|
||||
@inject IInstallationService InstallationService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject SiteState SiteState
|
||||
|
||||
@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; }
|
||||
|
||||
[CascadingParameter]
|
||||
HttpContext HttpContext { get; set; }
|
||||
|
||||
private bool _initialized = false;
|
||||
private string _display = "display: none;";
|
||||
private Installation _installation = new Installation { Success = false, Message = "" };
|
||||
|
||||
private PageState PageState { get; set; }
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
SiteState.RemoteIPAddress = RemoteIPAddress;
|
||||
SiteState.AntiForgeryToken = AntiForgeryToken;
|
||||
SiteState.AuthorizationToken = AuthorizationToken;
|
||||
SiteState.IsPrerendering = (HttpContext != null) ? true : false;
|
||||
|
||||
_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 Oqtane.Interfaces;
|
||||
using Oqtane.Providers;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
@ -7,16 +8,17 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class OqtaneServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddOqtaneAuthorization(this IServiceCollection services)
|
||||
public static IServiceCollection AddOqtaneAuthentication(this IServiceCollection services)
|
||||
{
|
||||
services.AddAuthorizationCore();
|
||||
services.AddCascadingAuthenticationState();
|
||||
services.AddScoped<IdentityAuthenticationStateProvider>();
|
||||
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddOqtaneScopedServices(this IServiceCollection services)
|
||||
public static IServiceCollection AddOqtaneClientScopedServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<SiteState>();
|
||||
services.AddScoped<IInstallationService, InstallationService>();
|
||||
@ -49,6 +51,13 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddScoped<IUrlMappingService, UrlMappingService>();
|
||||
services.AddScoped<IVisitorService, VisitorService>();
|
||||
services.AddScoped<ISyncService, SyncService>();
|
||||
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
|
||||
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
||||
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
||||
|
||||
// providers
|
||||
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
||||
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.TextAreaTextEditor>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
<div class="row">
|
||||
<div class="mx-auto text-center">
|
||||
<img src="oqtane-black.png" />
|
||||
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 8)</div>
|
||||
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 9)</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="app-rule" />
|
||||
@ -69,16 +69,16 @@
|
||||
<h2>@Localizer["ApplicationAdmin"]</h2><br />
|
||||
<div class="container">
|
||||
<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">
|
||||
<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 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>
|
||||
<div class="col-sm-9">
|
||||
<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>
|
||||
</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>
|
||||
<div class="col-sm-9">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -95,7 +95,13 @@
|
||||
<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>
|
||||
<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 class="row mb-1 align-items-center">
|
||||
@ -153,130 +159,134 @@
|
||||
private string _toggleConfirmPassword = string.Empty;
|
||||
private string _confirmPassword = string.Empty;
|
||||
private string _hostEmail = string.Empty;
|
||||
private string _hostName = string.Empty;
|
||||
private List<SiteTemplate> _templates;
|
||||
private string _template = Constants.DefaultSiteTemplate;
|
||||
private bool _register = true;
|
||||
private string _message = string.Empty;
|
||||
private string _loadingDisplay = "display: none;";
|
||||
private string _message = string.Empty;
|
||||
private string _loadingDisplay = "display: none;";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// 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);
|
||||
|
||||
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
|
||||
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
|
||||
|
||||
_databases = await DatabaseService.GetDatabasesAsync();
|
||||
if (_databases.Exists(item => item.IsDefault))
|
||||
{
|
||||
_databaseName = _databases.Find(item => item.IsDefault).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
_databaseName = "LocalDB";
|
||||
}
|
||||
LoadDatabaseConfigComponent();
|
||||
_databases = await DatabaseService.GetDatabasesAsync();
|
||||
if (_databases.Exists(item => item.IsDefault))
|
||||
{
|
||||
_databaseName = _databases.Find(item => item.IsDefault).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
_databaseName = "LocalDB";
|
||||
}
|
||||
LoadDatabaseConfigComponent();
|
||||
|
||||
_templates = await SiteTemplateService.GetSiteTemplatesAsync();
|
||||
}
|
||||
|
||||
private void DatabaseChanged(ChangeEventArgs eventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
_databaseName = (string)eventArgs.Value;
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_message = Localizer["Error.DbConfig.Load"];
|
||||
}
|
||||
}
|
||||
private void DatabaseChanged(ChangeEventArgs eventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
_databaseName = (string)eventArgs.Value;
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_message = Localizer["Error.DbConfig.Load"];
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDatabaseConfigComponent()
|
||||
{
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||
if (database != null)
|
||||
{
|
||||
_databaseConfigType = Type.GetType(database.ControlType);
|
||||
DatabaseConfigComponent = builder =>
|
||||
{
|
||||
builder.OpenComponent(0, _databaseConfigType);
|
||||
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
}
|
||||
private void LoadDatabaseConfigComponent()
|
||||
{
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||
if (database != null)
|
||||
{
|
||||
_databaseConfigType = Type.GetType(database.ControlType);
|
||||
DatabaseConfigComponent = builder =>
|
||||
{
|
||||
builder.OpenComponent(0, _databaseConfigType);
|
||||
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// include JavaScript
|
||||
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");
|
||||
}
|
||||
}
|
||||
var interop = new Interop(JSRuntime);
|
||||
await interop.IncludeScript("", Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous", "", "head");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Install()
|
||||
{
|
||||
var connectionString = String.Empty;
|
||||
if (_showConnectionString)
|
||||
{
|
||||
connectionString = _connectionString;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||
{
|
||||
connectionString = databaseConfigControl.GetConnectionString();
|
||||
}
|
||||
}
|
||||
private async Task Install()
|
||||
{
|
||||
var connectionString = String.Empty;
|
||||
if (_showConnectionString)
|
||||
{
|
||||
connectionString = _connectionString;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||
{
|
||||
connectionString = databaseConfigControl.GetConnectionString();
|
||||
}
|
||||
}
|
||||
|
||||
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
|
||||
{
|
||||
if (await UserService.ValidatePasswordAsync(_hostPassword))
|
||||
{
|
||||
_loadingDisplay = "";
|
||||
StateHasChanged();
|
||||
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@") && !string.IsNullOrEmpty(_hostName))
|
||||
{
|
||||
var result = await UserService.ValidateUserAsync(_hostUsername, _hostEmail, _hostPassword);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
_loadingDisplay = "";
|
||||
StateHasChanged();
|
||||
|
||||
Uri uri = new Uri(NavigationManager.Uri);
|
||||
Uri uri = new Uri(NavigationManager.Uri);
|
||||
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||
|
||||
var config = new InstallConfig
|
||||
{
|
||||
DatabaseType = database.DBType,
|
||||
ConnectionString = connectionString,
|
||||
Aliases = uri.Authority,
|
||||
HostUsername = _hostUsername,
|
||||
HostPassword = _hostPassword,
|
||||
HostEmail = _hostEmail,
|
||||
HostName = _hostUsername,
|
||||
TenantName = TenantNames.Master,
|
||||
IsNewTenant = true,
|
||||
SiteName = Constants.DefaultSite,
|
||||
Register = _register,
|
||||
SiteTemplate = _template
|
||||
};
|
||||
var config = new InstallConfig
|
||||
{
|
||||
DatabaseType = database.DBType,
|
||||
ConnectionString = connectionString,
|
||||
Aliases = uri.Authority,
|
||||
HostUsername = _hostUsername,
|
||||
HostPassword = _hostPassword,
|
||||
HostEmail = _hostEmail,
|
||||
HostName = _hostName,
|
||||
TenantName = TenantNames.Master,
|
||||
IsNewTenant = true,
|
||||
SiteName = Constants.DefaultSite,
|
||||
Register = _register,
|
||||
SiteTemplate = _template,
|
||||
RenderMode = RenderModes.Static,
|
||||
Runtime = Runtimes.Server
|
||||
};
|
||||
|
||||
var installation = await InstallationService.Install(config);
|
||||
if (installation.Success)
|
||||
{
|
||||
NavigationManager.NavigateTo(uri.Scheme + "://" + uri.Authority, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = installation.Message;
|
||||
_loadingDisplay = "display: none;";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = Localizer["Message.Password.Invalid"];
|
||||
var installation = await InstallationService.Install(config);
|
||||
if (installation.Success)
|
||||
{
|
||||
NavigationManager.NavigateTo(uri.Scheme + "://" + uri.Authority, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = installation.Message;
|
||||
_loadingDisplay = "display: none;";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = string.Join("<br />", result.Errors.Select(i => !string.IsNullOrEmpty(i.Value) ? i.Value : Localizer[i.Key]));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -12,12 +12,12 @@
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||
{
|
||||
string url = NavigateUrl(p.Path);
|
||||
<p 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-body" href="@url" Match="NavLinkMatch.All">
|
||||
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>
|
||||
<p class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</p>
|
||||
<div class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</div>
|
||||
</NavLink>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@ -27,6 +27,7 @@
|
||||
private List<Page> _pages;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||
public override string RenderMode => RenderModes.Static;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
|
@ -40,10 +40,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveFile">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
@if (_name.ToLower().EndsWith(".zip"))
|
||||
{
|
||||
<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>
|
||||
</form>
|
||||
}
|
||||
@ -126,4 +130,18 @@
|
||||
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,67 +8,83 @@
|
||||
|
||||
@if (_folders != null)
|
||||
{
|
||||
<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="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<option value="-1"><@Localizer["NoParent"]></option>
|
||||
}
|
||||
@foreach (Folder folder in _folders)
|
||||
{
|
||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||
}
|
||||
</select>
|
||||
<TabStrip>
|
||||
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
|
||||
<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="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<option value="-1"><@Localizer["NoParent"]></option>
|
||||
}
|
||||
@foreach (Folder folder in _folders)
|
||||
{
|
||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="type" HelpText="Select the folder type. Private folders are only accessible by authorized users. Public folders can be accessed by all users" ResourceKey="Type">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<input id="type" class="form-control" readonly @bind="@_type" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<select id="type" class="form-select" @bind="@_type" required>
|
||||
<option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option>
|
||||
<option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option>
|
||||
</select>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<input id="capacity" class="form-control" @bind="@_capacity" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="type" HelpText="Select the folder type. Private folders are only accessible by authorized users. Public folders can be accessed by all users" ResourceKey="Type">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<input id="type" class="form-control" readonly @bind="@_type" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<select id="type" class="form-select" @bind="@_type" required>
|
||||
<option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option>
|
||||
<option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option>
|
||||
</select>
|
||||
}
|
||||
</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">
|
||||
<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">
|
||||
<input id="capacity" class="form-control" @bind="@_capacity" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-12">
|
||||
<Label Class="col-sm-3" For="permissions" HelpText="Select the permissions you want for the folder" ResourceKey="Permissions">Permissions: </Label>
|
||||
@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" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br /><br />
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
|
||||
<br />
|
||||
@if (!_isSystem)
|
||||
{
|
||||
<button type="button" class="btn btn-success" @onclick="SaveFolder">@SharedLocalizer["Save"]</button>
|
||||
@ -80,11 +96,6 @@
|
||||
@((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" />
|
||||
}
|
||||
<br /><br />
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
@ -95,8 +106,9 @@
|
||||
private int _parentId = -1;
|
||||
private string _name;
|
||||
private string _type = FolderTypes.Private;
|
||||
private string _imagesizes = string.Empty;
|
||||
private string _capacity = "0";
|
||||
private string _cachecontrol = string.Empty;
|
||||
private string _imagesizes = string.Empty;
|
||||
private bool _isSystem;
|
||||
private List<Permission> _permissions = null;
|
||||
private string _createdBy;
|
||||
@ -127,8 +139,9 @@
|
||||
_parentId = folder.ParentId ?? -1;
|
||||
_name = folder.Name;
|
||||
_type = folder.Type;
|
||||
_imagesizes = folder.ImageSizes;
|
||||
_capacity = folder.Capacity.ToString();
|
||||
_cachecontrol = folder.CacheControl;
|
||||
_imagesizes = folder.ImageSizes;
|
||||
_isSystem = folder.IsSystem;
|
||||
_permissions = folder.PermissionList;
|
||||
_createdBy = folder.CreatedBy;
|
||||
@ -170,6 +183,7 @@
|
||||
try
|
||||
{
|
||||
Folder folder;
|
||||
|
||||
if (_folderId != -1)
|
||||
{
|
||||
folder = await FolderService.GetFolderAsync(_folderId);
|
||||
@ -179,8 +193,6 @@
|
||||
folder = new Folder();
|
||||
}
|
||||
|
||||
folder.SiteId = PageState.Site.SiteId;
|
||||
|
||||
if (_parentId == -1)
|
||||
{
|
||||
folder.ParentId = null;
|
||||
@ -190,10 +202,19 @@
|
||||
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.Type = _type;
|
||||
folder.ImageSizes = _imagesizes;
|
||||
folder.Capacity = int.Parse(_capacity);
|
||||
folder.CacheControl = _cachecontrol;
|
||||
folder.ImageSizes = _imagesizes;
|
||||
folder.IsSystem = _isSystem;
|
||||
folder.PermissionList = _permissionGrid.GetPermissionList();
|
||||
|
||||
|
@ -3,54 +3,92 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IFolderService FolderService
|
||||
@inject IFileService FileService
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@if (_files != null)
|
||||
@if (_files == null)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md mb-1">
|
||||
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
|
||||
</div>
|
||||
<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))">
|
||||
@foreach (Folder folder in _folders)
|
||||
{
|
||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||
}
|
||||
</select>
|
||||
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />
|
||||
<p>
|
||||
<em>@SharedLocalizer["Loading"]</em>
|
||||
</p>
|
||||
}
|
||||
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 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))">
|
||||
@foreach (Folder folder in _folders)
|
||||
{
|
||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||
}
|
||||
</select>
|
||||
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md mb-1 text-end">
|
||||
<ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md mb-1 text-end">
|
||||
<ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Pager Items="@_files" SearchProperties="Name">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
<th>@Localizer["Modified"]</th>
|
||||
<th>@Localizer["Type"]</th>
|
||||
<th>@Localizer["Size"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<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><a href="@context.Url" target="_new">@context.Name</a></td>
|
||||
<td>@context.ModifiedOn</td>
|
||||
<td>@context.Extension.ToUpper() @SharedLocalizer["File"]</td>
|
||||
<td>@string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
@if (_files.Count == 0)
|
||||
{
|
||||
<div class="text-center">@Localizer["NoFiles"]</div>
|
||||
}
|
||||
@if (_files.Count != 0)
|
||||
{
|
||||
<Pager Items="@_files" SearchProperties="Name">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
<th>@Localizer["Modified"]</th>
|
||||
<th>@Localizer["Type"]</th>
|
||||
<th>@Localizer["Size"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<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><a href="@context.Url" target="_new">@context.Name</a></td>
|
||||
<td>@context.ModifiedOn</td>
|
||||
<td>@context.Extension.ToUpper() @SharedLocalizer["File"]</td>
|
||||
<td>@string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
}
|
||||
else
|
||||
{
|
||||
<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 {
|
||||
@ -58,6 +96,10 @@
|
||||
private int _folderId = -1;
|
||||
private List<File> _files;
|
||||
|
||||
private string _imageFiles = string.Empty;
|
||||
private string _uploadableFiles = string.Empty;
|
||||
private int _maxChunkSize = 1;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
@ -71,6 +113,13 @@
|
||||
_folderId = _folders[0].FolderId;
|
||||
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)
|
||||
{
|
||||
@ -115,4 +164,23 @@
|
||||
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">
|
||||
<Label Class="col-sm-3" For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="runs-every" class="form-control" @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>
|
||||
<option value="m">@Localizer["Minute(s)"]</option>
|
||||
<option value="H">@Localizer["Hour(s)"]</option>
|
||||
@ -154,6 +154,11 @@
|
||||
|
||||
private async Task SaveJob()
|
||||
{
|
||||
if (!Utilities.ValidateEffectiveExpiryDates(_startDate, _endDate))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
|
@ -10,14 +10,11 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<ActionLink Action="Log" Class="btn btn-secondary" Text="View Logs" ResourceKey="ViewJobs" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="(async () => await Refresh())">@Localizer["Refresh.Text"]</button>
|
||||
<br />
|
||||
<button type="button" class="btn btn-secondary" @onclick="Refresh">@Localizer["Refresh.Text"]</button>
|
||||
<br />
|
||||
|
||||
<Pager Items="@_jobs" SearchProperties="Name">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
@ -27,9 +24,8 @@ else
|
||||
<th style="width: 1px;"> </th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="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" Class="btn btn-secondary" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="JobLog" /></td>
|
||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="EditJob" /></td>
|
||||
<td><ActionLink Action="Log" Text="Log" Class="btn btn-secondary" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="JobLog" /></td>
|
||||
<td>@context.Name</td>
|
||||
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
|
||||
<td>@DisplayFrequency(context.Interval, context.Frequency)</td>
|
||||
@ -46,21 +42,25 @@ else
|
||||
</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
|
||||
<br />
|
||||
<ActionLink Action="Log" Class="btn btn-secondary" Text="View All Logs" ResourceKey="ViewLogs" />
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Job> _jobs;
|
||||
private List<Job> _jobs;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
|
||||
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
_jobs = await JobService.GetJobsAsync();
|
||||
if (_jobs.Count == 0)
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.NoJobs"], NavigateUrl("admin/system")), MessageType.Warning);
|
||||
}
|
||||
}
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await GetJobs();
|
||||
}
|
||||
|
||||
private async Task GetJobs()
|
||||
{
|
||||
_jobs = await JobService.GetJobsAsync();
|
||||
}
|
||||
|
||||
private string DisplayStatus(bool isEnabled, bool isExecuting)
|
||||
{
|
||||
@ -112,22 +112,6 @@ else
|
||||
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)
|
||||
{
|
||||
try
|
||||
@ -164,7 +148,8 @@ else
|
||||
|
||||
private async Task Refresh()
|
||||
{
|
||||
_jobs = await JobService.GetJobsAsync();
|
||||
StateHasChanged();
|
||||
ShowProgressIndicator();
|
||||
await GetJobs();
|
||||
HideProgressIndicator();
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,9 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="Refresh">@Localizer["Refresh"]</button>
|
||||
<br /><br />
|
||||
|
||||
<Pager Items="@_jobLogs">
|
||||
<Header>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
@ -35,6 +38,11 @@ else
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
await GetJobLogs();
|
||||
}
|
||||
|
||||
private async Task GetJobLogs()
|
||||
{
|
||||
_jobLogs = await JobLogService.GetJobLogsAsync();
|
||||
|
||||
@ -67,4 +75,11 @@ else
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private async Task Refresh()
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
await GetJobLogs();
|
||||
HideProgressIndicator();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
@namespace Oqtane.Modules.Admin.Languages
|
||||
@inherits ModuleBase
|
||||
@using System.Globalization
|
||||
@using Microsoft.AspNetCore.Localization
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ILocalizationService LocalizationService
|
||||
@inject ILanguageService LanguageService
|
||||
@ -94,7 +93,6 @@ else
|
||||
var language = new Language
|
||||
{
|
||||
SiteId = PageState.Page.SiteId,
|
||||
Name = CultureInfo.GetCultureInfo(_code).DisplayName,
|
||||
Code = _code,
|
||||
IsDefault = (_default == null ? false : Boolean.Parse(_default))
|
||||
};
|
||||
@ -130,7 +128,7 @@ else
|
||||
{
|
||||
var interop = new Interop(JSRuntime);
|
||||
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
||||
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
|
||||
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
@namespace Oqtane.Modules.Admin.Languages
|
||||
@inherits ModuleBase
|
||||
@using System.Globalization
|
||||
@using Microsoft.AspNetCore.Localization
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ILocalizationService LocalizationService
|
||||
@inject ILanguageService LanguageService
|
||||
@ -103,7 +102,7 @@ else
|
||||
{
|
||||
var interop = new Interop(JSRuntime);
|
||||
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
||||
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
|
||||
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,66 +8,77 @@
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<AuthorizeView Roles="@RoleNames.Registered">
|
||||
<Authorizing>
|
||||
<text>...</text>
|
||||
</Authorizing>
|
||||
<NotAuthorized>
|
||||
@if (!twofactor)
|
||||
{
|
||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<br /><br />
|
||||
}
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
<div class="form-group">
|
||||
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
||||
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" required />
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" required />
|
||||
@if (PageState.User != null)
|
||||
{
|
||||
<ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (!twofactor)
|
||||
{
|
||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
}
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
<div class="form-group">
|
||||
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
||||
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
@if (!_alwaysremember)
|
||||
{
|
||||
<div class="form-check">
|
||||
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
|
||||
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container Oqtane-Modules-Admin-Login">
|
||||
<div class="form-group">
|
||||
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
|
||||
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
@if (!_alwaysremember)
|
||||
{
|
||||
<div class="form-check">
|
||||
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
|
||||
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||
@if (PageState.Site.AllowRegistration)
|
||||
{
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container Oqtane-Modules-Admin-Login">
|
||||
<div class="form-group">
|
||||
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
|
||||
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
private bool _allowsitelogin = true;
|
||||
@ -84,9 +95,8 @@
|
||||
private bool _alwaysremember = false;
|
||||
private string _code = string.Empty;
|
||||
|
||||
private string _returnUrl = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||
public override bool? Prerender => true;
|
||||
|
||||
public override List<Resource> Resources => new List<Resource>()
|
||||
{
|
||||
@ -103,11 +113,6 @@
|
||||
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
|
||||
if (PageState.QueryString.ContainsKey("returnurl"))
|
||||
{
|
||||
_returnUrl = PageState.QueryString["returnurl"];
|
||||
}
|
||||
|
||||
if (PageState.QueryString.ContainsKey("name"))
|
||||
{
|
||||
_username = PageState.QueryString["name"];
|
||||
@ -168,11 +173,14 @@
|
||||
{
|
||||
if (firstRender && PageState.User == null && _allowsitelogin)
|
||||
{
|
||||
await username.FocusAsync();
|
||||
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
|
||||
{
|
||||
await username.FocusAsync();
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
@ -199,28 +207,31 @@
|
||||
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)
|
||||
{
|
||||
// hybrid apps utilize an interactive login
|
||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||
authstateprovider.NotifyAuthenticationChanged();
|
||||
NavigationManager.NavigateTo(NavigateUrl(WebUtility.UrlDecode(_returnUrl), true));
|
||||
NavigationManager.NavigateTo(NavigateUrl(returnurl, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
// post back to the Login page so that the cookies are set correctly
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(returnurl) };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
}
|
||||
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;
|
||||
validated = false;
|
||||
@ -231,12 +242,12 @@
|
||||
if (!twofactor)
|
||||
{
|
||||
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
|
||||
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
|
||||
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username);
|
||||
AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
|
||||
AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -255,7 +266,7 @@
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
NavigationManager.NavigateTo(_returnUrl);
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
}
|
||||
|
||||
private async Task Forgot()
|
||||
@ -323,7 +334,7 @@
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ else
|
||||
<th>@Localizer["Function"]</th>
|
||||
</Header>
|
||||
<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)">@context.Level</td>
|
||||
<td class="@GetClass(context.Function)">@context.Feature</td>
|
||||
@ -86,47 +86,48 @@ else
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@code {
|
||||
private string _level = "-";
|
||||
private string _function = "-";
|
||||
private string _rows = "10";
|
||||
private int _page = 1;
|
||||
private List<Log> _logs;
|
||||
private int _retention = 30;
|
||||
private string _level = "-";
|
||||
private string _function = "-";
|
||||
private string _rows = "10";
|
||||
private int _page = 1;
|
||||
private List<Log> _logs;
|
||||
private int _retention = 30;
|
||||
|
||||
public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}";
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}";
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (UrlParameters.ContainsKey("level"))
|
||||
{
|
||||
_level = UrlParameters["level"];
|
||||
}
|
||||
if (UrlParameters.ContainsKey("function"))
|
||||
{
|
||||
_function = UrlParameters["function"];
|
||||
}
|
||||
if (UrlParameters.ContainsKey("rows"))
|
||||
{
|
||||
_rows = UrlParameters["rows"];
|
||||
}
|
||||
if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page))
|
||||
{
|
||||
_page = page;
|
||||
}
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (UrlParameters.ContainsKey("level"))
|
||||
{
|
||||
_level = UrlParameters["level"];
|
||||
}
|
||||
if (UrlParameters.ContainsKey("function"))
|
||||
{
|
||||
_function = UrlParameters["function"];
|
||||
}
|
||||
if (UrlParameters.ContainsKey("rows"))
|
||||
{
|
||||
_rows = UrlParameters["rows"];
|
||||
}
|
||||
if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page))
|
||||
{
|
||||
_page = page;
|
||||
}
|
||||
|
||||
await GetLogs();
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_retention = int.Parse( SettingService.GetSetting(settings, "LogRetention", "30"));
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_retention = int.Parse( SettingService.GetSetting(settings, "LogRetention", "30"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -213,22 +214,37 @@ else
|
||||
return classname;
|
||||
}
|
||||
|
||||
private async Task SaveSiteSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "LogRetention", _retention.ToString(), true);
|
||||
private async Task SaveSiteSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "LogRetention", _retention.ToString(), true);
|
||||
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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -125,7 +125,7 @@
|
||||
<TabPanel Name="Upload" ResourceKey="Upload" Heading="Upload">
|
||||
<div class="container">
|
||||
<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">
|
||||
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
|
||||
</div>
|
||||
|
@ -13,13 +13,13 @@
|
||||
<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="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">
|
||||
<input id="owner" class="form-control" @bind="@_owner" required />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<input id="module" class="form-control" @bind="@_module" required />
|
||||
</div>
|
||||
@ -27,7 +27,7 @@
|
||||
<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>
|
||||
<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 class="row mb-1 align-items-center">
|
||||
@ -111,6 +111,8 @@
|
||||
private async Task CreateModule()
|
||||
{
|
||||
validated = true;
|
||||
_owner = _owner.Trim();
|
||||
_module = _module.Trim();
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
@ -118,6 +120,7 @@
|
||||
{
|
||||
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
|
||||
{
|
||||
if (string.IsNullOrEmpty(_description)) _description = _module;
|
||||
if (IsValidXML(_description))
|
||||
{
|
||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||
|
@ -1,7 +1,6 @@
|
||||
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
||||
@inherits ModuleBase
|
||||
@using System.Globalization
|
||||
@using Microsoft.AspNetCore.Localization
|
||||
@inject IModuleDefinitionService ModuleDefinitionService
|
||||
@inject IPackageService PackageService
|
||||
@inject ILanguageService LanguageService
|
||||
@ -10,6 +9,7 @@
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@inject IPageModuleService PageModuleService
|
||||
@inject IModuleService ModuleService
|
||||
@inject IPageService PageService
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
@ -32,7 +32,7 @@
|
||||
<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>
|
||||
<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 class="row mb-1 align-items-center">
|
||||
@ -63,24 +63,7 @@
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
@if (!string.IsNullOrEmpty(_packagename))
|
||||
{
|
||||
<div class="input-group">
|
||||
<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 />
|
||||
}
|
||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -244,7 +227,6 @@
|
||||
private string _moduledefinitionname = "";
|
||||
private string _version;
|
||||
private string _packagename = "";
|
||||
private string _packageurl = "";
|
||||
private string _owner = "";
|
||||
private string _url = "";
|
||||
private string _contact = "";
|
||||
@ -306,15 +288,16 @@
|
||||
_languages = _languages.OrderBy(item => item.Name).ToList();
|
||||
}
|
||||
|
||||
// Group modules by PageId
|
||||
// Get distinct PageIds where modules are present
|
||||
var distinctPageIds = PageState.Modules
|
||||
.Where(md => md.ModuleDefinition.ModuleDefinitionId == _moduleDefinitionId && md.IsDeleted == false)
|
||||
// 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();
|
||||
|
||||
// Filter and retrieve the corresponding pages
|
||||
_pagesWithModules = PageState.Pages
|
||||
// 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();
|
||||
|
||||
@ -444,27 +427,5 @@
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
private string Browse(Page page) => string.IsNullOrEmpty(page.Url) ? NavigateUrl(page.Path) : page.Url;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IModuleService ModuleService
|
||||
@inject IModuleDefinitionService ModuleDefinitionService
|
||||
@inject IPackageService PackageService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@ -50,7 +51,7 @@ else
|
||||
<th style="width: 1px;"> </th>
|
||||
</Header>
|
||||
<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>
|
||||
@if (context.AssemblyName != Constants.ClientId)
|
||||
{
|
||||
@ -70,7 +71,7 @@ else
|
||||
}
|
||||
</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>
|
||||
}
|
||||
@ -99,6 +100,7 @@ else
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Module> _modules;
|
||||
private List<ModuleDefinition> _allModuleDefinitions;
|
||||
private List<ModuleDefinition> _moduleDefinitions;
|
||||
private List<Package> _packages;
|
||||
@ -111,6 +113,7 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
||||
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
|
||||
await LoadModuleDefinitions();
|
||||
|
@ -15,7 +15,7 @@
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-success" @onclick="ExportModule">@Localizer["Export"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
|
||||
@code {
|
||||
private string _content = string.Empty;
|
||||
|
@ -17,7 +17,7 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
@code {
|
||||
|
@ -3,121 +3,139 @@
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IThemeService ThemeService
|
||||
@inject IPageService PageService
|
||||
@inject IModuleService ModuleService
|
||||
@inject IPageModuleService PageModuleService
|
||||
@inject IStringLocalizer<Settings> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<TabStrip>
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||
@if (_containers != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="module" HelpText="The name of the module" ResourceKey="Module">Module: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="module" type="text" class="form-control" @bind="@_module" disabled />
|
||||
@if (_initialized)
|
||||
{
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<TabStrip ActiveTab="@_activetab">
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||
@if (_containers != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="module" HelpText="The name of the module" ResourceKey="Module">Module: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="module" type="text" class="form-control" @bind="@_module" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="title" type="text" class="form-control" @bind="@_title" required />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="title" type="text" class="form-control" @bind="@_title" required />
|
||||
</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">
|
||||
<Label Class="col-sm-3" For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="container" class="form-select" @bind="@_containerType" required>
|
||||
@foreach (var container in _containers)
|
||||
{
|
||||
<option value="@container.TypeName">@container.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<select id="allpages" class="form-select" @bind="@_allPages" 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="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="page" class="form-select" @bind="@_pageId" required>
|
||||
@if (PageState.Page.UserId != null)
|
||||
{
|
||||
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Page p in PageState.Pages)
|
||||
<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)
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
|
||||
<option value="@pane">@pane Pane</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<select id="container" class="form-select" @bind="@_containerType" required>
|
||||
@foreach (var container in _containers)
|
||||
{
|
||||
<option value="@container.TypeName">@container.Name</option>
|
||||
}
|
||||
</select>
|
||||
</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">
|
||||
<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">
|
||||
<select id="allpages" class="form-select" @bind="@_allPages" 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="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="page" class="form-select" @bind="@_pageId" required>
|
||||
@if (PageState.Page.UserId != null)
|
||||
{
|
||||
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_pages != null)
|
||||
{
|
||||
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
|
||||
foreach (Page p in _pages)
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
|
||||
{
|
||||
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</select>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Permissions" ResourceKey="Permissions">
|
||||
@if (_permissions != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Permissions" Heading="Permissions" ResourceKey="Permissions">
|
||||
@if (_permissions != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
</TabPanel>
|
||||
@if (_moduleSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ModuleSettings" Heading="@_moduleSettingsTitle" ResourceKey="ModuleSettings">
|
||||
@ModuleSettingsComponent
|
||||
</TabPanel>
|
||||
}
|
||||
</TabPanel>
|
||||
@if (_moduleSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ModuleSettings" Heading="@_moduleSettingsTitle" ResourceKey="ModuleSettings">
|
||||
@ModuleSettingsComponent
|
||||
</TabPanel>
|
||||
}
|
||||
@if (_containerSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ContainerSettings" Heading="Container Settings" ResourceKey="ContainerSettings">
|
||||
@ContainerSettingsComponent
|
||||
</TabPanel>
|
||||
}
|
||||
</TabStrip>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
||||
</form>
|
||||
@if (_containerSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ContainerSettings" Heading="Container Settings" ResourceKey="ContainerSettings">
|
||||
@ContainerSettingsComponent
|
||||
</TabPanel>
|
||||
}
|
||||
</TabStrip>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
||||
</form>
|
||||
}
|
||||
|
||||
@code {
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||
public override string Title => "Module Settings";
|
||||
|
||||
private bool _initialized = false;
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
@ -132,7 +150,7 @@
|
||||
private PermissionGrid _permissionGrid;
|
||||
private Type _moduleSettingsType;
|
||||
private object _moduleSettings;
|
||||
private string _moduleSettingsTitle = "Module Settings";
|
||||
private string _moduleSettingsTitle;
|
||||
private RenderFragment ModuleSettingsComponent { get; set; }
|
||||
private Type _containerSettingsType;
|
||||
private object _containerSettings;
|
||||
@ -141,11 +159,17 @@
|
||||
private DateTime createdon;
|
||||
private string modifiedby;
|
||||
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"];
|
||||
_pane = ModuleState.Pane;
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
|
||||
_containerType = ModuleState.ContainerType;
|
||||
@ -156,9 +180,13 @@
|
||||
createdon = ModuleState.CreatedOn;
|
||||
modifiedby = ModuleState.ModifiedBy;
|
||||
modifiedon = ModuleState.ModifiedOn;
|
||||
_effectivedate = Utilities.UtcAsLocalDate(ModuleState.EffectiveDate);
|
||||
_expirydate = Utilities.UtcAsLocalDate(ModuleState.ExpiryDate);
|
||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
|
||||
if (ModuleState.ModuleDefinition != null)
|
||||
{
|
||||
_module = ModuleState.ModuleDefinition.Name;
|
||||
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames;
|
||||
|
||||
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
|
||||
@ -182,7 +210,8 @@
|
||||
ModuleSettingsComponent = builder =>
|
||||
{
|
||||
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();
|
||||
};
|
||||
}
|
||||
@ -201,42 +230,54 @@
|
||||
ContainerSettingsComponent = builder =>
|
||||
{
|
||||
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();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
private async Task SaveModule()
|
||||
{
|
||||
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
|
||||
if (!string.IsNullOrEmpty(_title))
|
||||
{
|
||||
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
||||
pagemodule.PageId = int.Parse(_pageId);
|
||||
pagemodule.Title = _title;
|
||||
pagemodule.Pane = _pane;
|
||||
pagemodule.EffectiveDate = Utilities.LocalDateAndTimeAsUtc(_effectivedate);
|
||||
pagemodule.ExpiryDate = Utilities.LocalDateAndTimeAsUtc(_expirydate);
|
||||
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
|
||||
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
|
||||
{
|
||||
pagemodule.ContainerType = string.Empty;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType)
|
||||
{
|
||||
pagemodule.ContainerType = string.Empty;
|
||||
}
|
||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
|
||||
{
|
||||
pagemodule.ContainerType = string.Empty;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType)
|
||||
{
|
||||
pagemodule.ContainerType = string.Empty;
|
||||
}
|
||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
|
||||
|
||||
var module = ModuleState;
|
||||
module.AllPages = bool.Parse(_allPages);
|
||||
module.PageModuleId = ModuleState.PageModuleId;
|
||||
module.PermissionList = _permissionGrid.GetPermissionList();
|
||||
await ModuleService.UpdateModuleAsync(module);
|
||||
var module = ModuleState;
|
||||
module.AllPages = bool.Parse(_allPages);
|
||||
module.PageModuleId = ModuleState.PageModuleId;
|
||||
module.PermissionList = _permissionGrid.GetPermissionList();
|
||||
await ModuleService.UpdateModuleAsync(module);
|
||||
|
||||
if (_moduleSettingsType != null)
|
||||
{
|
||||
@ -257,17 +298,18 @@
|
||||
await containerSettingsControl.UpdateSettings();
|
||||
}
|
||||
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
_activetab = "Settings";
|
||||
AddModuleMessage(Localizer["Message.Required.Title"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_activetab = "Settings";
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" required />
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
@ -26,7 +26,7 @@
|
||||
<div class="col-sm-9">
|
||||
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
|
||||
<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))
|
||||
{
|
||||
@ -101,13 +101,13 @@
|
||||
<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>
|
||||
<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 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>
|
||||
<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 class="row mb-1 align-items-center">
|
||||
@ -119,6 +119,18 @@
|
||||
<i class="@_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -135,7 +147,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="title" class="form-control" @bind="@_title" />
|
||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -144,7 +156,14 @@
|
||||
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
||||
@foreach (var theme in _themes)
|
||||
{
|
||||
<option value="@theme.TypeName">@theme.Name</option>
|
||||
@if (theme.TypeName == PageState.Site.DefaultThemeType)
|
||||
{
|
||||
<option value="@theme.TypeName">*@theme.Name*</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@theme.TypeName">@theme.Name</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
@ -167,13 +186,13 @@
|
||||
<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>
|
||||
<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 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>
|
||||
<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>
|
||||
@ -186,12 +205,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
@if (_themeSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ThemeSettings" Heading=@Localizer["Theme.Heading"] ResourceKey="ThemeSettings">
|
||||
@ThemeSettingsComponent
|
||||
</TabPanel>
|
||||
}
|
||||
</TabStrip>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||
@ -207,6 +220,7 @@
|
||||
private bool validated = false;
|
||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
private List<Page> _pages;
|
||||
private int _pageId;
|
||||
private string _name;
|
||||
private string _parentid = "-1";
|
||||
@ -226,18 +240,19 @@
|
||||
private string _bodycontent;
|
||||
private string _permissions = null;
|
||||
private PermissionGrid _permissionGrid;
|
||||
private Type _themeSettingsType;
|
||||
private object _themeSettings;
|
||||
private RenderFragment ThemeSettingsComponent { get; set; }
|
||||
private bool _refresh = false;
|
||||
protected Page _parent = null;
|
||||
protected Dictionary<string, string> _icons;
|
||||
private string _iconresources = "";
|
||||
private DateTime? _effectivedate = null;
|
||||
private DateTime? _expirydate = null;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
|
||||
if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
||||
@ -258,14 +273,15 @@
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
_containertype = PageState.Site.DefaultContainerType;
|
||||
_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))
|
||||
{
|
||||
_children.Add(p);
|
||||
}
|
||||
}
|
||||
ThemeSettings();
|
||||
_effectivedate = Utilities.UtcAsLocalDate(PageState.Page.EffectiveDate);
|
||||
_expirydate = Utilities.UtcAsLocalDate(PageState.Page.ExpiryDate);
|
||||
_initialized = true;
|
||||
}
|
||||
else
|
||||
@ -274,20 +290,20 @@
|
||||
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Page {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Page {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async void ParentChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_parentid = (string)e.Value;
|
||||
private async void ParentChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_parentid = (string)e.Value;
|
||||
_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))
|
||||
{
|
||||
@ -295,154 +311,151 @@
|
||||
}
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
|
||||
private void ThemeChanged(ChangeEventArgs e)
|
||||
private async Task ThemeChanged(ChangeEventArgs e)
|
||||
{
|
||||
_themetype = (string)e.Value;
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
_containertype = _containers.First().TypeName;
|
||||
ThemeSettings();
|
||||
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()
|
||||
{
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SavePage()
|
||||
{
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
Page page = null;
|
||||
try
|
||||
{
|
||||
private async Task SavePage()
|
||||
{
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
Page page = null;
|
||||
try
|
||||
{
|
||||
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype))
|
||||
{
|
||||
page = new Page();
|
||||
page.SiteId = PageState.Page.SiteId;
|
||||
page.Name = _name;
|
||||
{
|
||||
page = new Page();
|
||||
page.SiteId = PageState.Page.SiteId;
|
||||
page.Name = _name;
|
||||
|
||||
if (string.IsNullOrEmpty(_path))
|
||||
{
|
||||
_path = _name;
|
||||
}
|
||||
if (_path.Contains("/"))
|
||||
{
|
||||
if (_path.EndsWith("/") && _path != "/")
|
||||
{
|
||||
_path = _path.Substring(0, _path.Length - 1);
|
||||
}
|
||||
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
||||
}
|
||||
if (_parentid == "-1")
|
||||
{
|
||||
page.ParentId = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
page.ParentId = Int32.Parse(_parentid);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_path))
|
||||
{
|
||||
_path = _name;
|
||||
}
|
||||
|
||||
if (_parentid == "-1")
|
||||
{
|
||||
page.ParentId = null;
|
||||
page.Path = Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
page.ParentId = Int32.Parse(_parentid);
|
||||
var parent = PageState.Pages.Where(item => item.PageId == page.ParentId).FirstOrDefault();
|
||||
if (parent.Path == string.Empty)
|
||||
{
|
||||
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
}
|
||||
if (_path.Contains("/"))
|
||||
{
|
||||
if (_path.EndsWith("/") && _path != "/")
|
||||
{
|
||||
_path = _path.Substring(0, _path.Length - 1);
|
||||
}
|
||||
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
||||
}
|
||||
if (_parentid == "-1")
|
||||
{
|
||||
page.Path = Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Page parent = _pages.FirstOrDefault(item => item.PageId == page.ParentId);
|
||||
if (parent.Path == string.Empty)
|
||||
{
|
||||
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
}
|
||||
|
||||
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
if (_pages.Any(item => item.Path == page.Path))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
if (_pages.Any(item => item.Path == page.Path))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
|
||||
Page child;
|
||||
switch (_insert)
|
||||
{
|
||||
case "<<":
|
||||
page.Order = 0;
|
||||
break;
|
||||
case "<":
|
||||
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
|
||||
page.Order = child.Order - 1;
|
||||
break;
|
||||
case ">":
|
||||
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
|
||||
page.Order = child.Order + 1;
|
||||
break;
|
||||
case ">>":
|
||||
page.Order = int.MaxValue;
|
||||
break;
|
||||
}
|
||||
Page child;
|
||||
switch (_insert)
|
||||
{
|
||||
case "<<":
|
||||
page.Order = 0;
|
||||
break;
|
||||
case "<":
|
||||
child = _pages.Where(item => item.PageId == _childid).FirstOrDefault();
|
||||
page.Order = child.Order - 1;
|
||||
break;
|
||||
case ">":
|
||||
child = _pages.Where(item => item.PageId == _childid).FirstOrDefault();
|
||||
page.Order = child.Order + 1;
|
||||
break;
|
||||
case ">>":
|
||||
page.Order = int.MaxValue;
|
||||
break;
|
||||
}
|
||||
|
||||
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
|
||||
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
|
||||
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
|
||||
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
|
||||
page.Url = _url;
|
||||
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
|
||||
page.EffectiveDate = Utilities.LocalDateAndTimeAsUtc(_effectivedate);
|
||||
page.ExpiryDate = Utilities.LocalDateAndTimeAsUtc(_expirydate);
|
||||
page.UserId = null;
|
||||
|
||||
// appearance
|
||||
page.Title = _title;
|
||||
page.Icon = (_icon == null ? string.Empty : _icon);
|
||||
page.ThemeType = _themetype;
|
||||
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
|
||||
{
|
||||
page.ThemeType = string.Empty;
|
||||
}
|
||||
page.DefaultContainerType = _containertype;
|
||||
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
|
||||
{
|
||||
page.DefaultContainerType = string.Empty;
|
||||
}
|
||||
page.ThemeType = _themetype;
|
||||
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
|
||||
{
|
||||
page.ThemeType = string.Empty;
|
||||
}
|
||||
page.DefaultContainerType = _containertype;
|
||||
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
|
||||
{
|
||||
page.DefaultContainerType = string.Empty;
|
||||
}
|
||||
|
||||
// page content
|
||||
page.HeadContent = _headcontent;
|
||||
page.BodyContent = _bodycontent;
|
||||
|
||||
// permissions
|
||||
page.PermissionList = _permissionGrid.GetPermissionList();
|
||||
page.PermissionList = _permissionGrid.GetPermissionList();
|
||||
|
||||
page = await PageService.AddPageAsync(page);
|
||||
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
|
||||
@ -450,7 +463,7 @@
|
||||
await logger.LogInformation("Page Added {Page}", page);
|
||||
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
|
||||
{
|
||||
@ -460,6 +473,7 @@
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
|
||||
}
|
||||
@ -467,11 +481,13 @@
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
@namespace Oqtane.Modules.Admin.Pages
|
||||
@using Oqtane.Interfaces
|
||||
@using System.Globalization
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IPageService PageService
|
||||
@inject IPageModuleService PageModuleService
|
||||
@inject IThemeService ThemeService
|
||||
@inject IModuleService ModuleService
|
||||
@inject IThemeService ThemeService
|
||||
@inject ISystemService SystemService
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@ -30,7 +32,7 @@
|
||||
<div class="col-sm-9">
|
||||
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
|
||||
<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)
|
||||
{
|
||||
@ -114,7 +116,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
||||
</div>
|
||||
@ -134,6 +136,18 @@
|
||||
<i class="@_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -158,7 +172,14 @@
|
||||
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
||||
@foreach (var theme in _themes)
|
||||
{
|
||||
<option value="@theme.TypeName">@theme.Name</option>
|
||||
@if (theme.TypeName == PageState.Site.DefaultThemeType)
|
||||
{
|
||||
<option value="@theme.TypeName">*@theme.Name*</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@theme.TypeName">@theme.Name</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
@ -167,9 +188,12 @@
|
||||
<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">
|
||||
<select id="container" class="form-select" @bind="@_containertype" required>
|
||||
@foreach (var container in _containers)
|
||||
@if (_containers != null)
|
||||
{
|
||||
<option value="@container.TypeName">@container.Name</option>
|
||||
foreach (var container in _containers)
|
||||
{
|
||||
<option value="@container.TypeName">@container.Name</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
@ -181,13 +205,13 @@
|
||||
<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>
|
||||
<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 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>
|
||||
<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>
|
||||
@ -222,7 +246,7 @@
|
||||
@if (_themeSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
|
||||
@ThemeSettingsComponent
|
||||
@_themeSettingsComponent
|
||||
</TabPanel>
|
||||
<br />
|
||||
}
|
||||
@ -239,13 +263,26 @@
|
||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="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">
|
||||
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="theme" class="form-select" @bind="@_themetype" required>
|
||||
@foreach (var theme in _themes)
|
||||
{
|
||||
<option value="@theme.TypeName">@theme.Name</option>
|
||||
@if (theme.TypeName == PageState.Site.DefaultThemeType)
|
||||
{
|
||||
<option value="@theme.TypeName">*@theme.Name*</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@theme.TypeName">@theme.Name</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
@ -266,7 +303,7 @@
|
||||
@if (_themeSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
|
||||
@ThemeSettingsComponent
|
||||
@_themeSettingsComponent
|
||||
</TabPanel>
|
||||
<br />
|
||||
}
|
||||
@ -286,6 +323,7 @@
|
||||
private bool validated = false;
|
||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
private List<Page> _pages;
|
||||
private int _pageId;
|
||||
private string _name;
|
||||
private string _currentparentid;
|
||||
@ -305,7 +343,7 @@
|
||||
private string _containertype = "-";
|
||||
private Type _themeSettingsType;
|
||||
private object _themeSettings;
|
||||
private RenderFragment ThemeSettingsComponent { get; set; }
|
||||
private RenderFragment _themeSettingsComponent { get; set; }
|
||||
private string _headcontent;
|
||||
private string _bodycontent;
|
||||
private List<Permission> _permissions = null;
|
||||
@ -322,11 +360,14 @@
|
||||
protected Page _parent = null;
|
||||
protected Dictionary<string, string> _icons;
|
||||
private string _iconresources = "";
|
||||
private DateTime? _effectivedate = null;
|
||||
private DateTime? _expirydate = null;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
||||
_page = await PageService.GetPageAsync(_pageId);
|
||||
_icons = await SystemService.GetIconsAsync();
|
||||
@ -342,10 +383,10 @@
|
||||
else
|
||||
{
|
||||
_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>();
|
||||
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))
|
||||
{
|
||||
@ -370,6 +411,8 @@
|
||||
}
|
||||
_url = _page.Url;
|
||||
_icon = _page.Icon;
|
||||
_effectivedate = Utilities.UtcAsLocalDate(_page.EffectiveDate);
|
||||
_expirydate = Utilities.UtcAsLocalDate(_page.ExpiryDate);
|
||||
_ispersonalizable = _page.IsPersonalizable.ToString();
|
||||
|
||||
// appearance
|
||||
@ -395,7 +438,8 @@
|
||||
_permissions = _page.PermissionList;
|
||||
|
||||
// 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
|
||||
_createdby = _page.CreatedBy;
|
||||
@ -427,7 +471,7 @@
|
||||
{
|
||||
_parentid = (string)e.Value;
|
||||
_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))
|
||||
{
|
||||
@ -441,37 +485,42 @@
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
|
||||
private void ThemeChanged(ChangeEventArgs e)
|
||||
private async Task ThemeChanged(ChangeEventArgs e)
|
||||
{
|
||||
_themetype = (string)e.Value;
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||
_containertype = _containers.First().TypeName;
|
||||
ThemeSettings();
|
||||
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()
|
||||
{
|
||||
_themeSettingsType = null;
|
||||
_themeSettingsComponent = 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 =>
|
||||
_themeSettingsComponent = builder =>
|
||||
{
|
||||
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();
|
||||
};
|
||||
}
|
||||
@ -487,16 +536,32 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
|
||||
{
|
||||
string currentPath = _page.Path;
|
||||
|
||||
_page.Name = _name;
|
||||
|
||||
if (_parentid == "-1")
|
||||
{
|
||||
_page.ParentId = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_page.ParentId = Int32.Parse(_parentid);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_path))
|
||||
{
|
||||
_path = _name;
|
||||
}
|
||||
|
||||
if (_path.Contains("/"))
|
||||
{
|
||||
if (_path.EndsWith("/") && _path != "/")
|
||||
@ -505,16 +570,13 @@
|
||||
}
|
||||
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
if (_parentid == "-1")
|
||||
{
|
||||
_page.ParentId = null;
|
||||
_page.Path = Utilities.GetFriendlyUrl(_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
_page.ParentId = Int32.Parse(_parentid);
|
||||
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == _page.ParentId);
|
||||
Page parent = _pages.FirstOrDefault(item => item.PageId == _page.ParentId);
|
||||
if (parent.Path == string.Empty)
|
||||
{
|
||||
_page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
||||
@ -525,16 +587,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
var _pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
if (_pages.Any(item => item.Path == _page.Path && item.PageId != _page.PageId))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_page.ParentId == null && Constants.ReservedRoutes.Contains(_page.Name.ToLower()))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], _page.Name), MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -547,11 +610,11 @@
|
||||
_page.Order = 0;
|
||||
break;
|
||||
case "<":
|
||||
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
|
||||
child = _pages.FirstOrDefault(item => item.PageId == _childid);
|
||||
if (child != null) _page.Order = child.Order - 1;
|
||||
break;
|
||||
case ">":
|
||||
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
|
||||
child = _pages.FirstOrDefault(item => item.PageId == _childid);
|
||||
if (child != null) _page.Order = child.Order + 1;
|
||||
break;
|
||||
case ">>":
|
||||
@ -563,6 +626,8 @@
|
||||
_page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
|
||||
_page.Url = _url;
|
||||
_page.Icon = _icon ?? string.Empty;
|
||||
_page.EffectiveDate = Utilities.LocalDateAndTimeAsUtc(_effectivedate);
|
||||
_page.ExpiryDate = Utilities.LocalDateAndTimeAsUtc(_expirydate);
|
||||
_page.IsPersonalizable = (_ispersonalizable != null && Boolean.Parse(_ispersonalizable));
|
||||
|
||||
// appearance
|
||||
@ -607,27 +672,30 @@
|
||||
await logger.LogInformation("Page Saved {Page}", _page);
|
||||
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl, true); // redirect to page being edited and reload
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Page {Page} {Error}", _page, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,11 +5,11 @@
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@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" />
|
||||
|
||||
<Pager Items="@PageState.Pages.Where(item => !item.IsDeleted)" SearchProperties="Name">
|
||||
<Pager Items="@_pages.Where(item => !item.IsDeleted)" SearchProperties="Name">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
@ -17,7 +17,7 @@
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
</Header>
|
||||
<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><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>
|
||||
@ -28,6 +28,21 @@
|
||||
@code {
|
||||
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)
|
||||
{
|
||||
try
|
||||
|
@ -34,7 +34,7 @@
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<input id="order" class="form-control" @bind="@_vieworder" min="0" max="99" type="number" required />
|
||||
<input id="order" class="form-control" @bind="@_vieworder" min="0" max="9999" type="number" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -76,6 +76,12 @@
|
||||
<input id="validation" class="form-control" @bind="@_validation" maxlength="200" />
|
||||
</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">
|
||||
<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">
|
||||
@ -89,7 +95,7 @@
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveProfile">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<br />
|
||||
<br />
|
||||
@ -111,6 +117,7 @@
|
||||
private string _defaultvalue = string.Empty;
|
||||
private string _options = string.Empty;
|
||||
private string _validation = string.Empty;
|
||||
private string _autocomplete = string.Empty;
|
||||
private string _isrequired = "False";
|
||||
private string _isprivate = "False";
|
||||
private string createdby;
|
||||
@ -142,6 +149,7 @@
|
||||
_defaultvalue = profile.DefaultValue;
|
||||
_options = profile.Options;
|
||||
_validation = profile.Validation;
|
||||
_autocomplete = profile.Autocomplete;
|
||||
_isrequired = profile.IsRequired.ToString();
|
||||
_isprivate = profile.IsPrivate.ToString();
|
||||
createdby = profile.CreatedBy;
|
||||
@ -187,8 +195,10 @@
|
||||
profile.DefaultValue = _defaultvalue;
|
||||
profile.Options = _options;
|
||||
profile.Validation = _validation;
|
||||
profile.Autocomplete = _autocomplete;
|
||||
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
|
||||
profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate));
|
||||
|
||||
if (_profileid != -1)
|
||||
{
|
||||
profile = await ProfileService.UpdateProfileAsync(profile);
|
||||
|
@ -22,7 +22,7 @@ else
|
||||
<th>@Localizer["Order"]</th>
|
||||
</Header>
|
||||
<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>@context.Name</td>
|
||||
<td>@context.Title</td>
|
||||
|
@ -13,71 +13,71 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<TabStrip>
|
||||
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
|
||||
@if (!_pages.Where(item => item.IsDeleted).Any())
|
||||
{
|
||||
<br />
|
||||
<p>@Localizer["NoPage.Deleted"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_pages.Where(item => item.IsDeleted)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
<th>@Localizer["DeletedBy"]</th>
|
||||
<th>@Localizer["DeletedOn"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<TabStrip>
|
||||
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
|
||||
@if (!_pages.Where(item => item.IsDeleted).Any())
|
||||
{
|
||||
<br />
|
||||
<p>@Localizer["NoPage.Deleted"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_pages.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Path"]</th>
|
||||
<th>@Localizer["DeletedBy"]</th>
|
||||
<th>@Localizer["DeletedOn"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<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>@context.Name</td>
|
||||
<td>@context.DeletedBy</td>
|
||||
<td>@context.DeletedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
<br />
|
||||
<ActionDialog Header="Remove All Deleted Pages" Message="Are You Sure You Wish To Permanently Remove All Deleted Pages?" Action="Remove All Deleted Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Modules" ResourceKey="Modules" Heading="Modules">
|
||||
@if (!_modules.Where(item => item.IsDeleted).Any())
|
||||
{
|
||||
<br />
|
||||
<p>@Localizer["NoModule.Deleted"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_modules.Where(item => item.IsDeleted)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["Page"]</th>
|
||||
<th>@Localizer["Module"]</th>
|
||||
<th>@Localizer["DeletedBy"]</th>
|
||||
<th>@Localizer["DeletedOn"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
||||
<td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
||||
<td>@_pages.Find(item => item.PageId == context.PageId).Name</td>
|
||||
<td>@context.Title</td>
|
||||
<td>@context.DeletedBy</td>
|
||||
<td>@context.DeletedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
<br />
|
||||
<ActionDialog Header="Remove All Deleted Modules" Message="Are You Sure You Wish To Permanently Remove All Deleted Modules?" Action="Remove All Deleted Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
|
||||
}
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
<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.Path</td>
|
||||
<td>@context.DeletedBy</td>
|
||||
<td>@context.DeletedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
<br />
|
||||
<ActionDialog Header="Remove All Deleted Pages" Message="Are You Sure You Wish To Permanently Remove All Deleted Pages?" Action="Remove All Deleted Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Modules" ResourceKey="Modules" Heading="Modules">
|
||||
@if (!_modules.Where(item => item.IsDeleted).Any())
|
||||
{
|
||||
<br />
|
||||
<p>@Localizer["NoModule.Deleted"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_modules.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["Page"]</th>
|
||||
<th>@Localizer["Module"]</th>
|
||||
<th>@Localizer["DeletedBy"]</th>
|
||||
<th>@Localizer["DeletedOn"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><button type="button" @onclick="@(() => RestoreModule(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
||||
<td><ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete"], context.Title)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
||||
<td>@_pages.Find(item => item.PageId == context.PageId).Name</td>
|
||||
<td>@context.Title</td>
|
||||
<td>@context.DeletedBy</td>
|
||||
<td>@context.DeletedOn</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
<br />
|
||||
<ActionDialog Header="Remove All Deleted Modules" Message="Are You Sure You Wish To Permanently Remove All Deleted Modules?" Action="Remove All Deleted Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
|
||||
}
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Page> _pages;
|
||||
private List<Module> _modules;
|
||||
private List<Page> _pages;
|
||||
private List<Module> _modules;
|
||||
private int _pagePage = 1;
|
||||
private int _pageModule = 1;
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
@ -105,12 +105,25 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
page.IsDeleted = false;
|
||||
await PageService.UpdatePageAsync(page);
|
||||
await logger.LogInformation("Page Restored {Page}", page);
|
||||
await Load();
|
||||
StateHasChanged();
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
var validated = true;
|
||||
if (page.ParentId != null)
|
||||
{
|
||||
var parent = _pages.Find(item => item.PageId == page.ParentId);
|
||||
validated = !parent.IsDeleted;
|
||||
}
|
||||
if (validated)
|
||||
{
|
||||
page.IsDeleted = false;
|
||||
await PageService.UpdatePageAsync(page);
|
||||
await logger.LogInformation("Page Restored {Page}", page);
|
||||
AddModuleMessage(Localizer["Success.Page.Restore"], MessageType.Success);
|
||||
await Load();
|
||||
StateHasChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Page.Restore"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -125,9 +138,9 @@ else
|
||||
{
|
||||
await PageService.DeletePageAsync(page.PageId);
|
||||
await logger.LogInformation("Page Permanently Deleted {Page}", page);
|
||||
AddModuleMessage(Localizer["Success.Page.Delete"], MessageType.Success);
|
||||
await Load();
|
||||
StateHasChanged();
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -140,7 +153,7 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
ModuleInstance.ShowProgressIndicator();
|
||||
ShowProgressIndicator();
|
||||
foreach (Page page in _pages.Where(item => item.IsDeleted))
|
||||
{
|
||||
await PageService.DeletePageAsync(page.PageId);
|
||||
@ -148,16 +161,16 @@ else
|
||||
}
|
||||
|
||||
await logger.LogInformation("Pages Permanently Deleted");
|
||||
AddModuleMessage(Localizer["Success.Pages.Delete"], MessageType.Success);
|
||||
await Load();
|
||||
ModuleInstance.HideProgressIndicator();
|
||||
HideProgressIndicator();
|
||||
StateHasChanged();
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Permanently Deleting Pages {Error}", ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
ModuleInstance.HideProgressIndicator();
|
||||
HideProgressIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,6 +182,7 @@ else
|
||||
pagemodule.IsDeleted = false;
|
||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||
await logger.LogInformation("Module Restored {Module}", module);
|
||||
AddModuleMessage(Localizer["Success.Module.Restore"], MessageType.Success);
|
||||
await Load();
|
||||
StateHasChanged();
|
||||
}
|
||||
@ -185,6 +199,7 @@ else
|
||||
{
|
||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||
await logger.LogInformation("Module Permanently Deleted {Module}", module);
|
||||
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
|
||||
await Load();
|
||||
StateHasChanged();
|
||||
}
|
||||
@ -199,21 +214,22 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
ModuleInstance.ShowProgressIndicator();
|
||||
ShowProgressIndicator();
|
||||
foreach (Module module in _modules.Where(item => item.IsDeleted).ToList())
|
||||
{
|
||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||
}
|
||||
await logger.LogInformation("Modules Permanently Deleted");
|
||||
AddModuleMessage(Localizer["Success.Modules.Delete"], MessageType.Success);
|
||||
await Load();
|
||||
ModuleInstance.HideProgressIndicator();
|
||||
HideProgressIndicator();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Permanently Deleting Modules {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Modules.Delete"], MessageType.Error);
|
||||
ModuleInstance.HideProgressIndicator();
|
||||
HideProgressIndicator();
|
||||
}
|
||||
}
|
||||
private void OnPageChangePage(int page)
|
||||
|
@ -9,14 +9,14 @@
|
||||
|
||||
@if (PageState.Site.AllowRegistration)
|
||||
{
|
||||
<AuthorizeView Roles="@RoleNames.Registered">
|
||||
<Authorizing>
|
||||
<text>...</text>
|
||||
</Authorizing>
|
||||
<Authorized>
|
||||
if (!_userCreated)
|
||||
{
|
||||
if (PageState.User != null)
|
||||
{
|
||||
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
@ -29,25 +29,25 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" 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 />
|
||||
<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>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered 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 />
|
||||
<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>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
|
||||
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -60,9 +60,16 @@
|
||||
<br />
|
||||
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</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>
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -80,12 +87,15 @@ else
|
||||
private string _confirm = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _displayname = string.Empty;
|
||||
private bool _userCreated = false;
|
||||
private bool _allowsitelogin = true;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
@ -121,14 +131,8 @@ else
|
||||
if (user != null)
|
||||
{
|
||||
await logger.LogInformation("User Created {Username} {Email}", _username, _email);
|
||||
if (PageState.QueryString.ContainsKey("returnurl"))
|
||||
{
|
||||
NavigationManager.NavigateTo(WebUtility.UrlDecode(PageState.QueryString["returnurl"]));
|
||||
}
|
||||
else // legacy behavior
|
||||
{
|
||||
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
|
||||
}
|
||||
_userCreated = true;
|
||||
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -160,7 +164,7 @@ else
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
}
|
||||
|
||||
private void TogglePassword()
|
||||
|
@ -20,9 +20,9 @@ else
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
</Header>
|
||||
<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><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>
|
||||
</Row>
|
||||
</Pager>
|
||||
|
@ -58,7 +58,7 @@ else
|
||||
<td>@context.EffectiveDate</td>
|
||||
<td>@context.ExpiryDate</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>
|
||||
</Row>
|
||||
</Pager>
|
||||
@ -180,27 +180,28 @@ else
|
||||
|
||||
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 logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
|
||||
AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success);
|
||||
await GetUserRoles();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Removing User From Role {UserRoleId} {Error}", UserRoleId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.User.RemoveRole"], MessageType.Error);
|
||||
await logger.LogInformation("User {Username} Removed From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||
}
|
||||
AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success);
|
||||
await GetUserRoles();
|
||||
StateHasChanged();
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
await logger.LogError(ex, "Error Removing User From Role {UserRoleId} {Error}", UserRoleId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.User.RemoveRole"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -71,20 +71,22 @@ else
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
||||
<option value="Server">@SharedLocalizer["BlazorServer"]</option>
|
||||
<option value="WebAssembly">@SharedLocalizer["BlazorWebAssembly"]</option>
|
||||
</select>
|
||||
<select id="rendermode" class="form-select" @bind="@_rendermode" required>
|
||||
<option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
|
||||
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
|
||||
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="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="prerender" class="form-select" @bind="@_prerender" required>
|
||||
<option value="Prerendered">@SharedLocalizer["Yes"]</option>
|
||||
<option value="">@SharedLocalizer["No"]</option>
|
||||
<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>
|
||||
@ -107,7 +109,7 @@ else
|
||||
<hr class="app-rule" />
|
||||
</div>
|
||||
<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">
|
||||
<input id="name" class="form-control" @bind="@_tenantName" maxlength="100" required />
|
||||
</div>
|
||||
@ -201,8 +203,8 @@ else
|
||||
private string _themetype = "-";
|
||||
private string _containertype = "-";
|
||||
private string _sitetemplatetype = "-";
|
||||
private string _runtime = "Server";
|
||||
private string _prerender = "Prerendered";
|
||||
private string _rendermode = RenderModes.Static;
|
||||
private string _runtime = Runtimes.Server;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
@ -228,137 +230,137 @@ else
|
||||
_sitetemplatetype = Constants.DefaultSiteTemplate;
|
||||
}
|
||||
|
||||
_databases = await DatabaseService.GetDatabasesAsync();
|
||||
if (_databases.Exists(item => item.IsDefault))
|
||||
{
|
||||
_databaseName = _databases.Find(item => item.IsDefault).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
_databaseName = "LocalDB";
|
||||
}
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
_databases = await DatabaseService.GetDatabasesAsync();
|
||||
if (_databases.Exists(item => item.IsDefault))
|
||||
{
|
||||
_databaseName = _databases.Find(item => item.IsDefault).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
_databaseName = "LocalDB";
|
||||
}
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
|
||||
private void DatabaseChanged(ChangeEventArgs eventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
_databaseName = (string)eventArgs.Value;
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
catch
|
||||
{
|
||||
AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
private void DatabaseChanged(ChangeEventArgs eventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
_databaseName = (string)eventArgs.Value;
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
catch
|
||||
{
|
||||
AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDatabaseConfigComponent()
|
||||
{
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||
if (database != null)
|
||||
{
|
||||
_databaseConfigType = Type.GetType(database.ControlType);
|
||||
DatabaseConfigComponent = builder =>
|
||||
{
|
||||
builder.OpenComponent(0, _databaseConfigType);
|
||||
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
}
|
||||
private void LoadDatabaseConfigComponent()
|
||||
{
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||
if (database != null)
|
||||
{
|
||||
_databaseConfigType = Type.GetType(database.ControlType);
|
||||
DatabaseConfigComponent = builder =>
|
||||
{
|
||||
builder.OpenComponent(0, _databaseConfigType);
|
||||
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void TenantChanged(ChangeEventArgs e)
|
||||
{
|
||||
_tenantid = (string)e.Value;
|
||||
if (string.IsNullOrEmpty(_tenantName))
|
||||
{
|
||||
_tenantName = _name;
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
private void TenantChanged(ChangeEventArgs e)
|
||||
{
|
||||
_tenantid = (string)e.Value;
|
||||
if (string.IsNullOrEmpty(_tenantName))
|
||||
{
|
||||
_tenantName = _name;
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async void ThemeChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_themetype = (string)e.Value;
|
||||
if (_themetype != "-")
|
||||
{
|
||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
||||
private async void ThemeChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_themetype = (string)e.Value;
|
||||
if (_themetype != "-")
|
||||
{
|
||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
||||
_containertype = _containers.First().TypeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
_containers = new List<ThemeControl>();
|
||||
else
|
||||
{
|
||||
_containers = new List<ThemeControl>();
|
||||
_containertype = "-";
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Theme.LoadContainers"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Theme.LoadContainers"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveSite()
|
||||
{
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
|
||||
{
|
||||
_urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
|
||||
var duplicates = new List<string>();
|
||||
var aliases = await AliasService.GetAliasesAsync();
|
||||
foreach (string name in _urls.Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (aliases.Exists(item => item.Name == name))
|
||||
{
|
||||
duplicates.Add(name);
|
||||
}
|
||||
}
|
||||
private async Task SaveSite()
|
||||
{
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
|
||||
{
|
||||
_urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
|
||||
var duplicates = new List<string>();
|
||||
var aliases = await AliasService.GetAliasesAsync();
|
||||
foreach (string name in _urls.Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (aliases.Exists(item => item.Name == name))
|
||||
{
|
||||
duplicates.Add(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicates.Count == 0)
|
||||
{
|
||||
InstallConfig config = new InstallConfig();
|
||||
if (duplicates.Count == 0)
|
||||
{
|
||||
InstallConfig config = new InstallConfig();
|
||||
|
||||
if (_tenantid == "+")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_tenantName) && !_tenants.Exists(item => item.Name == _tenantName))
|
||||
{
|
||||
// validate host credentials
|
||||
var user = new User();
|
||||
user.SiteId = PageState.Site.SiteId;
|
||||
user.Username = _hostusername;
|
||||
user.Password = _hostpassword;
|
||||
user.LastIPAddress = PageState.RemoteIPAddress;
|
||||
user = await UserService.LoginUserAsync(user, false, false);
|
||||
if (_tenantid == "+")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_tenantName) && !_tenants.Exists(item => item.Name == _tenantName))
|
||||
{
|
||||
// validate host credentials
|
||||
var user = new User();
|
||||
user.SiteId = PageState.Site.SiteId;
|
||||
user.Username = _hostusername;
|
||||
user.Password = _hostpassword;
|
||||
user.LastIPAddress = PageState.RemoteIPAddress;
|
||||
user = await UserService.LoginUserAsync(user, false, false);
|
||||
if (user.IsAuthenticated)
|
||||
{
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||
var connectionString = String.Empty;
|
||||
if (_showConnectionString)
|
||||
{
|
||||
connectionString = _connectionString;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||
{
|
||||
connectionString = databaseConfigControl.GetConnectionString();
|
||||
}
|
||||
}
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||
var connectionString = String.Empty;
|
||||
if (_showConnectionString)
|
||||
{
|
||||
connectionString = _connectionString;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||
{
|
||||
connectionString = databaseConfigControl.GetConnectionString();
|
||||
}
|
||||
}
|
||||
|
||||
if (connectionString != "")
|
||||
{
|
||||
config.TenantName = _tenantName;
|
||||
config.DatabaseType = database.DBType;
|
||||
config.ConnectionString = connectionString;
|
||||
config.HostUsername = _hostusername;
|
||||
config.HostUsername = _hostusername;
|
||||
config.HostPassword = _hostpassword;
|
||||
config.HostEmail = user.Email;
|
||||
config.HostName = user.DisplayName;
|
||||
@ -399,8 +401,8 @@ else
|
||||
config.DefaultContainer = _containertype;
|
||||
config.DefaultAdminContainer = "";
|
||||
config.SiteTemplate = _sitetemplatetype;
|
||||
config.RenderMode = _rendermode;
|
||||
config.Runtime = _runtime;
|
||||
config.RenderMode = _runtime + _prerender;
|
||||
|
||||
ShowProgressIndicator();
|
||||
|
||||
|
@ -48,11 +48,25 @@ else
|
||||
|
||||
private void Edit(string name)
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + name + "/admin/site", true);
|
||||
if (PageState.Alias.Name == name)
|
||||
{
|
||||
NavigationManager.NavigateTo("/admin/site");
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + name + "/admin/site", true);
|
||||
}
|
||||
}
|
||||
|
||||
private void Browse(string name)
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + name, true);
|
||||
if (PageState.Alias.Name == name)
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.Alias.Path + "/");
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + name, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,24 +83,15 @@ else
|
||||
{
|
||||
@if (_connection != "-")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="databasetype" HelpText="The database type" ResourceKey="DatabaseType">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (_databases != null)
|
||||
{
|
||||
<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>
|
||||
@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>
|
||||
<div class="col-sm-9">
|
||||
<input id="databasetype" class="form-control" @bind="@_databasetype" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="tenant" HelpText="The database using this connection" ResourceKey="Tenant">Database: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
|
||||
@ -204,12 +195,12 @@ else
|
||||
{
|
||||
_connectionstring = _connections[_connection].ToString();
|
||||
_tenant = "";
|
||||
_databasetype = "-";
|
||||
_databasetype = "";
|
||||
var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection);
|
||||
if (tenant != null)
|
||||
{
|
||||
_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
|
||||
|
@ -26,6 +26,12 @@
|
||||
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="process" HelpText="Indicates if the current process is 32 bit or 64 bit" ResourceKey="Process">Process: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="process" class="form-control" @bind="@_process" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -62,12 +68,6 @@
|
||||
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="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">
|
||||
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
|
||||
<div class="col-sm-9">
|
||||
@ -133,16 +133,30 @@
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packageregistryemail" HelpText="Specify The Email Address Of The User Account Used For Interacting With The Package Manager Service. This Account Is Used For Managing Packages Across Multiple Installations." ResourceKey="PackageManagerEmail">Package Manager Email: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="packageregistryemail" class="form-control" @bind="@_packageregistryemail" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>
|
||||
<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" />
|
||||
<br /><br />
|
||||
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Swagger"]</a>
|
||||
<a class="btn btn-secondary" href="api/endpoint" target="_new">@Localizer["Endpoints"]</a>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
|
||||
<div class="container">
|
||||
@ -165,13 +179,13 @@
|
||||
private string _version = string.Empty;
|
||||
private string _clrversion = string.Empty;
|
||||
private string _osversion = string.Empty;
|
||||
private string _machinename = string.Empty;
|
||||
private string _process = string.Empty;
|
||||
private string _machinename = string.Empty;
|
||||
private string _ipaddress = string.Empty;
|
||||
private string _environment = string.Empty;
|
||||
private string _contentrootpath = string.Empty;
|
||||
private string _webrootpath = string.Empty;
|
||||
private string _servertime = string.Empty;
|
||||
private string _tickcount = string.Empty;
|
||||
private string _workingset = string.Empty;
|
||||
private string _installationid = string.Empty;
|
||||
|
||||
@ -179,9 +193,11 @@
|
||||
private string _logginglevel = string.Empty;
|
||||
private string _notificationlevel = string.Empty;
|
||||
private string _swagger = string.Empty;
|
||||
private string _cachecontrol = string.Empty;
|
||||
private string _packageregistryurl = string.Empty;
|
||||
private string _packageregistryemail = string.Empty;
|
||||
|
||||
private string _log = string.Empty;
|
||||
private string _log = string.Empty;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@ -192,13 +208,13 @@
|
||||
{
|
||||
_clrversion = systeminfo["CLRVersion"].ToString();
|
||||
_osversion = systeminfo["OSVersion"].ToString();
|
||||
_machinename = systeminfo["MachineName"].ToString();
|
||||
_process = systeminfo["Process"].ToString();
|
||||
_machinename = systeminfo["MachineName"].ToString();
|
||||
_ipaddress = systeminfo["IPAddress"].ToString();
|
||||
_environment = systeminfo["Environment"].ToString();
|
||||
_contentrootpath = systeminfo["ContentRootPath"].ToString();
|
||||
_webrootpath = systeminfo["WebRootPath"].ToString();
|
||||
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
|
||||
_tickcount = TimeSpan.FromMilliseconds(Convert.ToInt64(systeminfo["TickCount"].ToString())).ToString();
|
||||
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
|
||||
}
|
||||
|
||||
@ -209,9 +225,11 @@
|
||||
_detailederrors = systeminfo["DetailedErrors"].ToString();
|
||||
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
|
||||
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
|
||||
_swagger = systeminfo["UseSwagger"].ToString();
|
||||
_swagger = systeminfo["UseSwagger"].ToString();
|
||||
_cachecontrol = systeminfo["CacheControl"].ToString();
|
||||
_packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
|
||||
}
|
||||
_packageregistryemail = systeminfo["PackageRegistryEmail"].ToString();
|
||||
}
|
||||
|
||||
systeminfo = await SystemService.GetSystemInfoAsync("log");
|
||||
if (systeminfo != null)
|
||||
@ -229,8 +247,10 @@
|
||||
settings.Add("Logging:LogLevel:Default", _logginglevel);
|
||||
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
|
||||
settings.Add("UseSwagger", _swagger);
|
||||
settings.Add("PackageRegistryUrl", _packageregistryurl);
|
||||
await SystemService.UpdateSystemInfoAsync(settings);
|
||||
settings.Add("CacheControl", _cachecontrol);
|
||||
settings.Add("PackageRegistryUrl", _packageregistryurl);
|
||||
settings.Add("PackageRegistryEmail", _packageregistryemail);
|
||||
await SystemService.UpdateSystemInfoAsync(settings);
|
||||
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -125,7 +125,7 @@
|
||||
<TabPanel Name="Upload" ResourceKey="Upload">
|
||||
<div class="container">
|
||||
<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">
|
||||
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
|
||||
</div>
|
||||
|
@ -45,24 +45,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (!string.IsNullOrEmpty(_packagename))
|
||||
{
|
||||
<div class="input-group">
|
||||
<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 />
|
||||
}
|
||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -116,7 +99,6 @@
|
||||
private string _name;
|
||||
private string _version;
|
||||
private string _packagename = "";
|
||||
private string _packageurl = "";
|
||||
private string _owner = "";
|
||||
private string _url = "";
|
||||
private string _contact = "";
|
||||
@ -185,27 +167,4 @@
|
||||
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 IThemeService ThemeService
|
||||
@inject IPackageService PackageService
|
||||
@inject ISiteService SiteService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@ -19,6 +20,7 @@ else
|
||||
|
||||
<Pager Items="@_themes">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["Name"]</th>
|
||||
@ -29,13 +31,14 @@ else
|
||||
<th> </th>
|
||||
</Header>
|
||||
<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>
|
||||
@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" />
|
||||
}
|
||||
}
|
||||
</td>
|
||||
<td><NavLink class="btn btn-secondary" href="@NavigateUrl("admin/site")">@Localizer["Assign"]</NavLink></td>
|
||||
<td>@context.Name</td>
|
||||
<td>@context.Version</td>
|
||||
<td>
|
||||
@ -63,7 +66,6 @@ else
|
||||
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadTheme(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
|
||||
}
|
||||
</td>
|
||||
<td></td>
|
||||
</Row>
|
||||
</Pager>
|
||||
}
|
||||
|
@ -54,6 +54,8 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Disclaimer.Text"], MessageType.Warning);
|
||||
|
||||
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
|
||||
if (packages != null)
|
||||
{
|
||||
@ -97,13 +99,16 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
await PackageService.DownloadPackageAsync(packageid, version);
|
||||
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version);
|
||||
HideProgressIndicator();
|
||||
AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Downloading Framework Package {Error}", ex.Message);
|
||||
HideProgressIndicator();
|
||||
AddModuleMessage(Localizer["Error.Framework.Download"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IUrlMappingService UrlMappingService
|
||||
@inject ISiteService SiteService
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@ -37,7 +38,7 @@ else
|
||||
<th>@Localizer["Requested"]</th>
|
||||
</Header>
|
||||
<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>
|
||||
<a href="@Utilities.TenantUrl(PageState.Alias, context.Url)">@context.Url</a>
|
||||
@ -62,7 +63,13 @@ else
|
||||
</select>
|
||||
</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>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
</TabPanel>
|
||||
@ -73,6 +80,7 @@ else
|
||||
private bool _mapped = true;
|
||||
private List<UrlMapping> _urlMappings;
|
||||
private string _capturebrokenurls;
|
||||
private int _retention = 30;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
@ -80,7 +88,10 @@ else
|
||||
{
|
||||
await GetUrlMappings();
|
||||
_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)
|
||||
{
|
||||
@ -124,7 +135,12 @@ else
|
||||
var site = PageState.Site;
|
||||
site.CaptureBrokenUrls = bool.Parse(_capturebrokenurls);
|
||||
await SiteService.UpdateSiteAsync(site);
|
||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||
|
||||
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);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -9,6 +9,8 @@
|
||||
@inject INotificationService NotificationService
|
||||
@inject IFileService FileService
|
||||
@inject IFolderService FolderService
|
||||
@inject IJSRuntime jsRuntime
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@ -84,6 +86,7 @@
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<button type="button" class="btn btn-danger" @onclick="Logout">@Localizer["Logout Everywhere"]</button>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||
<div class="container">
|
||||
@ -102,45 +105,100 @@
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||
<div class="col-sm-9">
|
||||
@if (!string.IsNullOrEmpty(p.Options))
|
||||
<div class="col-sm-9">
|
||||
@if (!string.IsNullOrEmpty(p.Options))
|
||||
{
|
||||
<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))
|
||||
@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))
|
||||
{
|
||||
<option value="@option" selected>@option</option>
|
||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||
{
|
||||
<option value="@option" selected>@option</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@option">@option</option>
|
||||
}
|
||||
}
|
||||
else
|
||||
</select>
|
||||
}
|
||||
else
|
||||
{
|
||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
<option value="@option">@option</option>
|
||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||
{
|
||||
<option value="@option" selected>@option</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@option">@option</option>
|
||||
}
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</select>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (p.Rows == 1)
|
||||
{
|
||||
@if (p.IsRequired)
|
||||
if (!string.IsNullOrEmpty(p.Autocomplete))
|
||||
{
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
|
||||
@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
|
||||
{
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
||||
@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 (p.IsRequired)
|
||||
if (!string.IsNullOrEmpty(p.Autocomplete))
|
||||
{
|
||||
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
|
||||
@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
|
||||
{
|
||||
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
|
||||
@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>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -168,11 +226,11 @@
|
||||
{
|
||||
<Pager Items="@notifications">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["From"]</th>
|
||||
<th>@Localizer["Subject"]</th>
|
||||
<th>@Localizer["Received"]</th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["From"]</th>
|
||||
<th>@Localizer["Subject"]</th>
|
||||
<th>@Localizer["Received"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||
@ -180,13 +238,13 @@
|
||||
|
||||
@if (context.IsRead)
|
||||
{
|
||||
<td>@context.FromDisplayName</td>
|
||||
<td>@(string.IsNullOrEmpty(context.FromDisplayName) ? SharedLocalizer["System"] : context.FromDisplayName)</td>
|
||||
<td>@context.Subject</td>
|
||||
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
|
||||
}
|
||||
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>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td>
|
||||
}
|
||||
@ -231,11 +289,11 @@
|
||||
{
|
||||
<Pager Items="@notifications">
|
||||
<Header>
|
||||
<th style="width: 1px;"></th>
|
||||
<th style="width: 1px;"></th>
|
||||
<th>@Localizer["To"]</th>
|
||||
<th>@Localizer["Subject"]</th>
|
||||
<th>@Localizer["Sent"]</th>
|
||||
<th style="width: 1px;"></th>
|
||||
<th style="width: 1px;"></th>
|
||||
<th>@Localizer["To"]</th>
|
||||
<th>@Localizer["Subject"]</th>
|
||||
<th>@Localizer["Sent"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||
@ -313,7 +371,7 @@
|
||||
private File photo = null;
|
||||
private string _ImageFiles = string.Empty;
|
||||
private List<Profile> profiles;
|
||||
private Dictionary<string, string> settings;
|
||||
private Dictionary<string, string> userSettings;
|
||||
private string category = string.Empty;
|
||||
|
||||
private string filter = "to";
|
||||
@ -360,10 +418,11 @@
|
||||
photofileid = -1;
|
||||
photo = null;
|
||||
}
|
||||
var sitesettings = await SettingService.GetSiteSettingsAsync(SiteState.Alias.SiteId);
|
||||
_ImageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
||||
|
||||
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
||||
userSettings = PageState.User.Settings;
|
||||
var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_ImageFiles = SettingService.GetSetting(userSettings, "ImageFiles", Constants.ImageFiles);
|
||||
_ImageFiles = (string.IsNullOrEmpty(_ImageFiles)) ? Constants.ImageFiles : _ImageFiles;
|
||||
|
||||
await LoadNotificationsAsync();
|
||||
|
||||
@ -389,7 +448,7 @@
|
||||
|
||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||
{
|
||||
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
|
||||
string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue);
|
||||
if (value.Contains("]"))
|
||||
{
|
||||
value = value.Substring(value.IndexOf("]") + 1);
|
||||
@ -432,12 +491,12 @@
|
||||
user = await UserService.UpdateUserAsync(user);
|
||||
if (user != null)
|
||||
{
|
||||
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
|
||||
await SettingService.UpdateUserSettingsAsync(userSettings, PageState.User.UserId);
|
||||
await logger.LogInformation("User Profile Saved");
|
||||
|
||||
if (PageState.QueryString.ContainsKey("returnurl"))
|
||||
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||
{
|
||||
NavigationManager.NavigateTo(WebUtility.UrlDecode(PageState.QueryString["returnurl"]));
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
}
|
||||
else // legacy behavior
|
||||
{
|
||||
@ -470,6 +529,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
foreach (Profile profile in profiles)
|
||||
@ -477,7 +562,7 @@
|
||||
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))
|
||||
{
|
||||
@ -503,13 +588,13 @@
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
}
|
||||
|
||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||
{
|
||||
var value = (string)e.Value;
|
||||
settings = SettingService.SetSetting(settings, SettingName, value);
|
||||
userSettings = SettingService.SetSetting(userSettings, SettingName, value);
|
||||
}
|
||||
|
||||
private async Task Delete(Notification Notification)
|
||||
@ -548,7 +633,7 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
ModuleInstance.ShowProgressIndicator();
|
||||
ShowProgressIndicator();
|
||||
foreach(var Notification in notifications)
|
||||
{
|
||||
if (!Notification.IsDeleted)
|
||||
@ -561,34 +646,32 @@
|
||||
await NotificationService.DeleteNotificationAsync(Notification.NotificationId);
|
||||
}
|
||||
await logger.LogInformation("Notification Deleted {Notification}", Notification);
|
||||
}
|
||||
}
|
||||
await logger.LogInformation("Notifications Permanently Deleted");
|
||||
await LoadNotificationsAsync();
|
||||
ModuleInstance.HideProgressIndicator();
|
||||
HideProgressIndicator();
|
||||
|
||||
StateHasChanged();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting Notifications {Error}", ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
ModuleInstance.HideProgressIndicator();
|
||||
HideProgressIndicator();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void TogglePassword()
|
||||
{
|
||||
if (_passwordtype == "password")
|
||||
{
|
||||
_passwordtype = "text";
|
||||
_togglepassword = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_passwordtype = "password";
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (_passwordtype == "password")
|
||||
{
|
||||
_passwordtype = "text";
|
||||
_togglepassword = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_passwordtype = "password";
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +128,7 @@
|
||||
createdon = notification.CreatedOn.ToString();
|
||||
body = notification.Body;
|
||||
|
||||
if (title == "From")
|
||||
if (title == "From" && !notification.IsRead)
|
||||
{
|
||||
notification.IsRead = true;
|
||||
notification = await NotificationService.UpdateNotificationAsync(notification);
|
||||
|
@ -14,7 +14,6 @@
|
||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||
@if (profiles != null)
|
||||
{
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<div class="container">
|
||||
<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>
|
||||
@ -22,24 +21,6 @@
|
||||
<input id="username" class="form-control" @bind="@_username" />
|
||||
</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">
|
||||
<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">
|
||||
@ -100,11 +81,11 @@
|
||||
{
|
||||
@if (p.Rows == 1)
|
||||
{
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @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
|
||||
{
|
||||
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
|
||||
<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>
|
||||
@ -123,12 +104,7 @@
|
||||
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private string _passwordrequirements;
|
||||
private string _username = string.Empty;
|
||||
private string _password = string.Empty;
|
||||
private string _passwordtype = "password";
|
||||
private string _togglepassword = string.Empty;
|
||||
private string _confirm = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _displayname = string.Empty;
|
||||
private string _notify = "True";
|
||||
@ -142,8 +118,6 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||
settings = new Dictionary<string, string>();
|
||||
_initialized = true;
|
||||
@ -169,39 +143,32 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_username != string.Empty && _password != string.Empty && _confirm != string.Empty && _email != string.Empty)
|
||||
if (_username != string.Empty && _email != string.Empty)
|
||||
{
|
||||
if (_password == _confirm)
|
||||
if (ValidateProfiles())
|
||||
{
|
||||
if (ValidateProfiles())
|
||||
var user = new User();
|
||||
user.SiteId = PageState.Site.SiteId;
|
||||
user.Username = _username;
|
||||
user.Password = ""; // will be auto generated
|
||||
user.Email = _email;
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||
user.PhotoFileId = null;
|
||||
user.SuppressNotification = !bool.Parse(_notify);
|
||||
|
||||
user = await UserService.AddUserAsync(user);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
var user = new User();
|
||||
user.SiteId = PageState.Site.SiteId;
|
||||
user.Username = _username;
|
||||
user.Password = _password;
|
||||
user.Email = _email;
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||
user.PhotoFileId = null;
|
||||
user.SuppressNotification = !bool.Parse(_notify);
|
||||
|
||||
user = await UserService.AddUserAsync(user);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
|
||||
await logger.LogInformation("User Created {User}", user);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
else
|
||||
{
|
||||
await logger.LogError("Error Adding User {Username} {Email}", _username, _email);
|
||||
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
|
||||
}
|
||||
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
|
||||
await logger.LogInformation("User Created {User}", user);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
else
|
||||
{
|
||||
await logger.LogError("Error Adding User {Username} {Email}", _username, _email);
|
||||
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -252,18 +219,4 @@
|
||||
var value = (string)e.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,6 +6,7 @@
|
||||
@inject IProfileService ProfileService
|
||||
@inject ISettingService SettingService
|
||||
@inject IFileService FileService
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@ -51,15 +52,18 @@
|
||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
||||
</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="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
@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"></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"></Label>
|
||||
<div class="col-sm-9">
|
||||
@ -89,8 +93,8 @@
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||
<div class="col-sm-9">
|
||||
@if (!string.IsNullOrEmpty(p.Options))
|
||||
<div class="col-sm-9">
|
||||
@if (!string.IsNullOrEmpty(p.Options))
|
||||
{
|
||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
@ -110,11 +114,11 @@
|
||||
{
|
||||
@if (p.Rows == 1)
|
||||
{
|
||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @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
|
||||
{
|
||||
<textarea id="@p.Name" class="form-control" maxlength="@p.MaxLength" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"></textarea>
|
||||
<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>
|
||||
@ -127,12 +131,19 @@
|
||||
|
||||
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !ishost)
|
||||
{
|
||||
<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 bool _initialized = false;
|
||||
private string _passwordrequirements;
|
||||
private int userid;
|
||||
@ -146,9 +157,10 @@
|
||||
private string isdeleted;
|
||||
private string lastlogin;
|
||||
private string lastipaddress;
|
||||
private bool ishost = false;
|
||||
|
||||
private List<Profile> profiles;
|
||||
private Dictionary<string, string> settings;
|
||||
private Dictionary<string, string> userSettings;
|
||||
private string category = string.Empty;
|
||||
|
||||
private string createdby;
|
||||
@ -180,8 +192,9 @@
|
||||
isdeleted = user.IsDeleted.ToString();
|
||||
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
|
||||
lastipaddress = user.LastIPAddress;
|
||||
ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
||||
|
||||
settings = await SettingService.GetUserSettingsAsync(user.UserId);
|
||||
userSettings = user.Settings;
|
||||
createdby = user.CreatedBy;
|
||||
createdon = user.CreatedOn;
|
||||
modifiedby = user.ModifiedBy;
|
||||
@ -202,7 +215,7 @@
|
||||
|
||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||
{
|
||||
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
|
||||
string value = SettingService.GetSetting(userSettings, SettingName, DefaultValue);
|
||||
if (value.Contains("]"))
|
||||
{
|
||||
value = value.Substring(value.IndexOf("]") + 1);
|
||||
@ -226,18 +239,15 @@
|
||||
user.Password = _password;
|
||||
user.Email = email;
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
|
||||
user.PhotoFileId = null;
|
||||
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);
|
||||
if (user != null)
|
||||
{
|
||||
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
|
||||
await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId);
|
||||
await logger.LogInformation("User Saved {User}", user);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
@ -264,6 +274,44 @@
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
foreach (Profile profile in profiles)
|
||||
@ -271,7 +319,7 @@
|
||||
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))
|
||||
{
|
||||
@ -298,7 +346,7 @@
|
||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||
{
|
||||
var value = (string)e.Value;
|
||||
settings = SettingService.SetSetting(settings, SettingName, value);
|
||||
userSettings = SettingService.SetSetting(userSettings, SettingName, value);
|
||||
}
|
||||
|
||||
private void TogglePassword()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -34,13 +34,13 @@ else
|
||||
<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>
|
||||
<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 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>
|
||||
<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>
|
||||
|
||||
@ -53,17 +53,17 @@ else
|
||||
<p align="center">
|
||||
<Pager Items="@userroles">
|
||||
<Header>
|
||||
<th>@Localizer["Roles"]</th>
|
||||
<th>@Localizer["Effective"]</th>
|
||||
<th>@Localizer["Expiry"]</th>
|
||||
<th> </th>
|
||||
<th>@Localizer["Roles"]</th>
|
||||
<th>@Localizer["Effective"]</th>
|
||||
<th>@Localizer["Expiry"]</th>
|
||||
<th> </th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td>@context.Role.Name</td>
|
||||
<td>@context.EffectiveDate</td>
|
||||
<td>@context.ExpiryDate</td>
|
||||
<td>@Utilities.UtcAsLocalDate(context.EffectiveDate)</td>
|
||||
<td>@Utilities.UtcAsLocalDate(context.ExpiryDate)</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>
|
||||
</Row>
|
||||
</Pager>
|
||||
@ -75,8 +75,8 @@ else
|
||||
private string name = string.Empty;
|
||||
private List<Role> roles;
|
||||
private int roleid = -1;
|
||||
private DateTime? effectivedate = null;
|
||||
private DateTime? expirydate = null;
|
||||
private DateTime? _effectivedate = null;
|
||||
private DateTime? _expirydate = null;
|
||||
private List<UserRole> userroles;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||
@ -92,7 +92,7 @@ else
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId, true);
|
||||
roles.RemoveAll(item => item.Name == RoleNames.Everyone || item.Name == RoleNames.Unauthenticated);
|
||||
roles.RemoveAll(item => item.Name == RoleNames.Everyone || item.Name == RoleNames.Unauthenticated);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -113,6 +113,7 @@ else
|
||||
try
|
||||
{
|
||||
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, userid);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -127,11 +128,16 @@ else
|
||||
{
|
||||
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();
|
||||
if (userrole != null)
|
||||
{
|
||||
userrole.EffectiveDate = effectivedate;
|
||||
userrole.ExpiryDate = expirydate;
|
||||
userrole.EffectiveDate = _effectivedate;
|
||||
userrole.ExpiryDate = _expirydate;
|
||||
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||
}
|
||||
else
|
||||
@ -139,15 +145,15 @@ else
|
||||
userrole = new UserRole();
|
||||
userrole.UserId = userid;
|
||||
userrole.RoleId = roleid;
|
||||
userrole.EffectiveDate = effectivedate;
|
||||
userrole.ExpiryDate = expirydate;
|
||||
userrole.EffectiveDate = Utilities.UtcAsLocalDate(_effectivedate);
|
||||
userrole.ExpiryDate = Utilities.UtcAsLocalDate(_expirydate);
|
||||
await UserRoleService.AddUserRoleAsync(userrole);
|
||||
}
|
||||
|
||||
await logger.LogInformation("User Assigned To Role {UserRole}", userrole);
|
||||
AddModuleMessage(Localizer["Success.User.AssignRole"], MessageType.Success);
|
||||
await GetUserRoles();
|
||||
StateHasChanged();
|
||||
StateHasChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -165,8 +171,18 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
||||
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
|
||||
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 logger.LogInformation("User {Username} Removed From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||
}
|
||||
AddModuleMessage(Localizer["Success.User.Remove"], MessageType.Success);
|
||||
await GetUserRoles();
|
||||
StateHasChanged();
|
||||
|
@ -43,7 +43,7 @@ else
|
||||
<th>@Localizer["Created"]</th>
|
||||
</Header>
|
||||
<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>
|
||||
@if (context.UserId != null)
|
||||
@ -69,14 +69,20 @@ else
|
||||
</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="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">
|
||||
<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">
|
||||
<textarea id="filter" class="form-control" @bind="@_filter" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||
</div>
|
||||
@ -103,7 +109,8 @@ else
|
||||
private int _page = 1;
|
||||
private List<Visitor> _visitors;
|
||||
private string _tracking;
|
||||
private string _filter = "";
|
||||
private int _duration = 5;
|
||||
private string _filter = "";
|
||||
private int _retention = 30;
|
||||
private string _correlation = "true";
|
||||
|
||||
@ -128,7 +135,8 @@ else
|
||||
|
||||
_tracking = PageState.Site.VisitorTracking.ToString();
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
|
||||
_duration = int.Parse(SettingService.GetSetting(settings, "VisitorDuration", "5"));
|
||||
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
|
||||
_retention = int.Parse(SettingService.GetSetting(settings, "VisitorRetention", "30"));
|
||||
_correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true");
|
||||
}
|
||||
@ -179,7 +187,8 @@ else
|
||||
await SiteService.UpdateSiteAsync(site);
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true);
|
||||
settings = SettingService.SetSetting(settings, "VisitorDuration", _duration.ToString(), true);
|
||||
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true);
|
||||
settings = SettingService.SetSetting(settings, "VisitorRetention", _retention.ToString(), true);
|
||||
settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true);
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||
|
@ -2,50 +2,107 @@
|
||||
@using System.Text.Json
|
||||
@inherits LocalizableComponent
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@if (_visible)
|
||||
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
|
||||
{
|
||||
<div class="app-actiondialog">
|
||||
<div class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@Header</h5>
|
||||
<button type="button" class="btn-close" aria-label="Close" @onclick="DisplayModal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>@Message</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@if (!string.IsNullOrEmpty(Action))
|
||||
{
|
||||
<button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
|
||||
}
|
||||
<button type="button" class="btn btn-secondary" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
|
||||
@if (_visible)
|
||||
{
|
||||
<div class="app-actiondialog">
|
||||
<div class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@Header</h5>
|
||||
<button type="button" class="btn-close" aria-label="Close" @onclick="DisplayModal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>@Message</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@if (!string.IsNullOrEmpty(Action))
|
||||
{
|
||||
<button type="button" class="@ConfirmClass" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
|
||||
}
|
||||
<button type="button" class="@CancelClass" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_authorized)
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
<button type="button" class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button>
|
||||
}
|
||||
else
|
||||
@if (_authorized)
|
||||
{
|
||||
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_iconSpan) @Text</button>
|
||||
if (Disabled)
|
||||
{
|
||||
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<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>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
private bool _visible = false;
|
||||
private List<Permission> _permissions;
|
||||
private List<Permission> _permissions;
|
||||
private bool _editmode = false;
|
||||
private bool _authorized = false;
|
||||
private string _iconSpan = string.Empty;
|
||||
private string _openIconSpan = string.Empty;
|
||||
private string _openText = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string Header { get; set; } // required
|
||||
@ -62,15 +119,21 @@
|
||||
[Parameter]
|
||||
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
|
||||
|
||||
[Parameter]
|
||||
public string Permissions { get; set; } // deprecated - use PermissionList instead
|
||||
[Parameter]
|
||||
public string Permissions { get; set; } // deprecated - use PermissionList instead
|
||||
|
||||
[Parameter]
|
||||
public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions
|
||||
[Parameter]
|
||||
public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions
|
||||
|
||||
[Parameter]
|
||||
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]
|
||||
public bool Disabled { get; set; } // optional
|
||||
|
||||
@ -83,15 +146,21 @@
|
||||
[Parameter]
|
||||
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Permissions))
|
||||
{
|
||||
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
[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()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Permissions))
|
||||
{
|
||||
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
base.OnParametersSet();
|
||||
|
||||
@ -99,31 +168,60 @@
|
||||
{
|
||||
Text = Action;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Class))
|
||||
{
|
||||
Class = "btn btn-success";
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(ConfirmClass))
|
||||
{
|
||||
ConfirmClass = Class;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(CancelClass))
|
||||
{
|
||||
CancelClass = "btn btn-secondary";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(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);
|
||||
Header = Localize(nameof(Header), Header);
|
||||
Message = Localize(nameof(Message), Message);
|
||||
|
||||
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
|
||||
_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;
|
||||
_authorized = IsAuthorized();
|
||||
|
||||
if (string.IsNullOrEmpty(Id)) Id = "1";
|
||||
|
||||
if (PageState.QueryString.ContainsKey("dialog"))
|
||||
{
|
||||
_visible = (PageState.QueryString["dialog"] == Id);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsAuthorized()
|
||||
@ -175,12 +273,22 @@
|
||||
private void DisplayModal()
|
||||
{
|
||||
_visible = !_visible;
|
||||
StateHasChanged();
|
||||
if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
|
||||
{
|
||||
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()
|
||||
{
|
||||
DisplayModal();
|
||||
OnClick();
|
||||
DisplayModal();
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
{
|
||||
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
|
||||
{
|
||||
@ -97,10 +97,13 @@
|
||||
{
|
||||
base.OnParametersSet();
|
||||
|
||||
_text = Action;
|
||||
if (!string.IsNullOrEmpty(Text))
|
||||
{
|
||||
_text = Text;
|
||||
_text = Localize(nameof(Text), Text);
|
||||
}
|
||||
else
|
||||
{
|
||||
_text = Localize(nameof(Action), Action);
|
||||
}
|
||||
|
||||
if (IconOnly && !string.IsNullOrEmpty(IconName))
|
||||
@ -142,7 +145,10 @@
|
||||
|
||||
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;
|
||||
}
|
||||
@ -150,7 +156,6 @@
|
||||
}
|
||||
|
||||
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
|
||||
_text = Localize(nameof(Text), _text);
|
||||
|
||||
_url = EditUrl(_path, _moduleId, Action, _parameters);
|
||||
if (!string.IsNullOrEmpty(ReturnUrl))
|
||||
|
@ -3,6 +3,7 @@
|
||||
@inherits ModuleControlBase
|
||||
@inject IFolderService FolderService
|
||||
@inject IFileService FileService
|
||||
@inject IUserService UserService
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<FileManager> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@ -156,6 +157,9 @@
|
||||
[Parameter]
|
||||
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]
|
||||
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
||||
|
||||
@ -195,7 +199,7 @@
|
||||
else
|
||||
{
|
||||
FolderId = -1;
|
||||
_message = "Folder Path " + Folder + "Does Not Exist";
|
||||
_message = "Folder Path " + Folder + " Does Not Exist";
|
||||
_messagetype = MessageType.Error;
|
||||
}
|
||||
}
|
||||
@ -225,9 +229,9 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
FileId = -1; // file does not exist
|
||||
_message = "FileId " + FileId.ToString() + "Does Not Exist";
|
||||
_message = "FileId " + FileId.ToString() + " Does Not Exist";
|
||||
_messagetype = MessageType.Error;
|
||||
FileId = -1; // file does not exist
|
||||
}
|
||||
}
|
||||
|
||||
@ -313,7 +317,6 @@
|
||||
await SetImage();
|
||||
await OnSelect.InvokeAsync(FileId);
|
||||
StateHasChanged();
|
||||
|
||||
}
|
||||
|
||||
private async Task SetImage()
|
||||
@ -344,7 +347,7 @@
|
||||
_message = string.Empty;
|
||||
var interop = new Interop(JSRuntime);
|
||||
var uploads = await interop.GetFiles(_fileinputid);
|
||||
|
||||
|
||||
if (uploads.Length > 0)
|
||||
{
|
||||
string restricted = "";
|
||||
@ -359,63 +362,42 @@
|
||||
}
|
||||
if (restricted == "")
|
||||
{
|
||||
if (!ShowProgress)
|
||||
{
|
||||
_uploading = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
// upload the files
|
||||
var posturl = Utilities.TenantUrl(PageState.Alias, "/api/file/upload");
|
||||
var folder = (Folder == Constants.PackagesFolder) ? Folder : FolderId.ToString();
|
||||
await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken);
|
||||
|
||||
// uploading is asynchronous so we need to poll to determine if uploads are completed
|
||||
var success = true;
|
||||
int upload = 0;
|
||||
while (upload < uploads.Length && success)
|
||||
var jwt = "";
|
||||
if (PageState.Runtime == Shared.Runtime.Hybrid)
|
||||
{
|
||||
success = false;
|
||||
var filename = uploads[upload].Split(':')[0];
|
||||
|
||||
var size = Int64.Parse(uploads[upload].Split(':')[1]); // bytes
|
||||
var megabits = (size / 1048576.0) * 8; // binary conversion
|
||||
var uploadspeed = 2; // 2 Mbps (3G ranges from 300Kbps to 3Mbps)
|
||||
var uploadtime = (megabits / uploadspeed); // seconds
|
||||
var maxattempts = 5; // polling (minimum timeout duration will be 5 seconds)
|
||||
var sleep = (int)Math.Ceiling(uploadtime / maxattempts) * 1000; // milliseconds
|
||||
|
||||
int attempts = 0;
|
||||
while (attempts < maxattempts && !success)
|
||||
jwt = await UserService.GetTokenAsync();
|
||||
if (string.IsNullOrEmpty(jwt))
|
||||
{
|
||||
attempts += 1;
|
||||
Thread.Sleep(sleep);
|
||||
|
||||
if (Folder == Constants.PackagesFolder)
|
||||
{
|
||||
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++;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
var chunksize = ChunkSize;
|
||||
if (chunksize == 1)
|
||||
{
|
||||
// if ChunkSize parameter is not overridden use the site setting
|
||||
chunksize = int.Parse(SettingService.GetSetting(PageState.Site.Settings, "MaxChunkSize", "1"));
|
||||
}
|
||||
|
||||
if (!ShowProgress)
|
||||
{
|
||||
_uploading = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
// upload files
|
||||
var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, tokenSource.Token);
|
||||
|
||||
// reset progress indicators
|
||||
if (ShowProgress)
|
||||
{
|
||||
@ -439,7 +421,7 @@
|
||||
}
|
||||
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"];
|
||||
_messagetype = MessageType.Error;
|
||||
}
|
||||
@ -469,6 +451,10 @@
|
||||
_message = Localizer["Error.File.Upload"];
|
||||
_messagetype = MessageType.Error;
|
||||
_uploading = false;
|
||||
await tokenSource.CancelAsync();
|
||||
}
|
||||
finally {
|
||||
tokenSource.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -38,13 +38,10 @@
|
||||
|
||||
protected void OnChange(ChangeEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Value.ToString()))
|
||||
Value = e.Value.ToString();
|
||||
if (ValueChanged.HasDelegate)
|
||||
{
|
||||
Value = e.Value.ToString();
|
||||
if (ValueChanged.HasDelegate)
|
||||
{
|
||||
ValueChanged.InvokeAsync(Value);
|
||||
}
|
||||
ValueChanged.InvokeAsync(Value);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,15 +2,25 @@
|
||||
@inherits ModuleControlBase
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@if (!string.IsNullOrEmpty(_message))
|
||||
@if (!string.IsNullOrEmpty(Message))
|
||||
{
|
||||
<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))
|
||||
{
|
||||
@((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>
|
||||
}
|
||||
|
||||
@ -24,6 +34,9 @@
|
||||
[Parameter]
|
||||
public MessageType Type { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderModeBoundary Parent { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
_message = Message;
|
||||
@ -54,10 +67,15 @@
|
||||
|
||||
return classname;
|
||||
}
|
||||
|
||||
private void DismissModal()
|
||||
private void CloseMessage(MouseEventArgs e)
|
||||
{
|
||||
_message = "";
|
||||
StateHasChanged();
|
||||
if(Parent != null)
|
||||
{
|
||||
Parent.DismissMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,63 +6,132 @@
|
||||
|
||||
@if (ItemList != null)
|
||||
{
|
||||
@if (!string.IsNullOrEmpty(SearchProperties))
|
||||
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
|
||||
{
|
||||
<div class="input-group my-3">
|
||||
<input id="search" 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>
|
||||
@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)
|
||||
{
|
||||
<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" @onclick=@(async () => UpdateList(1))><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" @onclick=@(async () => SkipPages("back"))><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" @onclick=@(async () => NavigateToPage("previous"))><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" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="page-item app-pager-pointer">
|
||||
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("next"))><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" @onclick=@(async () => SkipPages("forward"))><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" @onclick=@(async () => UpdateList(_pages))><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>
|
||||
}
|
||||
}
|
||||
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 ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
|
||||
{
|
||||
<ul class="pagination justify-content-center my-2">
|
||||
<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>
|
||||
</li>
|
||||
@if (_pages > _displayPages && _displayPages > 1)
|
||||
{
|
||||
<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>
|
||||
</li>
|
||||
}
|
||||
<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>
|
||||
</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" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="page-item app-pager-pointer">
|
||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
<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>
|
||||
</li>
|
||||
@if (_pages > _displayPages && _displayPages > 1)
|
||||
{
|
||||
<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>
|
||||
</li>
|
||||
}
|
||||
<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>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
@if (Format == "Table" && Row != null)
|
||||
{
|
||||
<div class="table-responsive">
|
||||
@ -126,53 +195,106 @@
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if ((Toolbar == "Bottom" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
|
||||
{
|
||||
<ul class="pagination justify-content-center my-2">
|
||||
<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>
|
||||
</li>
|
||||
@if (_pages > _displayPages && _displayPages > 1)
|
||||
{
|
||||
<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>
|
||||
@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")">
|
||||
<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 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>
|
||||
</li>
|
||||
@for (int i = _startPage; i <= _endPage; i++)
|
||||
{
|
||||
var pager = i;
|
||||
if (pager == _page)
|
||||
@if (_pages > _displayPages && _displayPages > 1)
|
||||
{
|
||||
<li class="page-item app-pager-pointer active">
|
||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||
<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>
|
||||
}
|
||||
else
|
||||
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||
</li>
|
||||
@for (int i = _startPage; i <= _endPage; i++)
|
||||
{
|
||||
<li class="page-item app-pager-pointer">
|
||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
var pager = i;
|
||||
if (pager == _page)
|
||||
{
|
||||
<li class="page-item app-pager-pointer active">
|
||||
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="page-item app-pager-pointer">
|
||||
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<a class="page-link shadow-none" @onclick=@(async () => NavigateToPage("next"))><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" @onclick=@(async () => SkipPages("forward"))><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" @onclick=@(async () => NavigateToPage("next"))><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" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||
<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 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>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||
</li>
|
||||
</ul>
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||
</li>
|
||||
</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>
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,6 +359,21 @@
|
||||
[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; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
@ -292,6 +429,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (PageState.QueryString.ContainsKey("search"))
|
||||
{
|
||||
_search = PageState.QueryString["search"];
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(SearchProperties))
|
||||
{
|
||||
AllItems = Items; // only used in search
|
||||
@ -316,13 +458,20 @@
|
||||
_displayPages = int.Parse(DisplayPages);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(CurrentPage))
|
||||
if (PageState.QueryString.ContainsKey("page") && int.TryParse(PageState.QueryString["page"], out int page))
|
||||
{
|
||||
_page = int.Parse(CurrentPage);
|
||||
_page = page;
|
||||
}
|
||||
else
|
||||
{
|
||||
_page = 1;
|
||||
if (!string.IsNullOrEmpty(CurrentPage))
|
||||
{
|
||||
_page = int.Parse(CurrentPage);
|
||||
}
|
||||
else
|
||||
{
|
||||
_page = 1;
|
||||
}
|
||||
}
|
||||
if (_page < 1) _page = 1;
|
||||
|
||||
@ -465,4 +614,25 @@
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +111,7 @@
|
||||
[Parameter]
|
||||
public List<Permission> PermissionList { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Permissions))
|
||||
{
|
||||
|
@ -4,11 +4,11 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Oqtane.Modules.Controls
|
||||
{
|
||||
public class RichTextEditorInterop
|
||||
public class QuillEditorInterop
|
||||
{
|
||||
private readonly IJSRuntime _jsRuntime;
|
||||
|
||||
public RichTextEditorInterop(IJSRuntime jsRuntime)
|
||||
public QuillEditorInterop(IJSRuntime 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
|
||||
{
|
||||
_jsRuntime.InvokeAsync<object>(
|
||||
"Oqtane.RichTextEditor.insertQuillImage",
|
||||
quillElement, imageUrl, altText);
|
||||
quillElement, imageUrl, altText, editorIndex);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
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,126 +1,22 @@
|
||||
@using System.Text.RegularExpressions
|
||||
@using Microsoft.AspNetCore.Components.Rendering
|
||||
@using Microsoft.Extensions.DependencyInjection
|
||||
@namespace Oqtane.Modules.Controls
|
||||
@inherits ModuleControlBase
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<RichTextEditor> Localizer
|
||||
|
||||
<div class="row" style="margin-bottom: 50px;">
|
||||
<div class="col">
|
||||
<TabStrip>
|
||||
@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 (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="@PageState.Site.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>
|
||||
@_textEditorComponent
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private ElementReference _editorElement;
|
||||
private ElementReference _toolBar;
|
||||
private bool _richfilemanager = false;
|
||||
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;
|
||||
private string _textEditorType;
|
||||
private RenderFragment _textEditorComponent;
|
||||
private ITextEditor _textEditor;
|
||||
|
||||
[Parameter]
|
||||
public string Content { get; set; }
|
||||
@ -129,170 +25,103 @@
|
||||
public bool ReadOnly { get; set; } = false;
|
||||
|
||||
[Parameter]
|
||||
public string Placeholder { get; set; } = "Enter Your Content...";
|
||||
public string Placeholder { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool AllowFileManagement { get; set; } = true;
|
||||
public string Provider { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool AllowRichText { get; set; } = true;
|
||||
[Parameter(CaptureUnmatchedValues = true)]
|
||||
public Dictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
[Parameter]
|
||||
public bool AllowRawHtml { get; set; } = true;
|
||||
|
||||
// parameters only applicable to rich text editor
|
||||
[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>()
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js" },
|
||||
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" }
|
||||
};
|
||||
_textEditorType = GetTextEditorType();
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
_richhtml = Content;
|
||||
_rawhtml = Content;
|
||||
_originalrawhtml = _rawhtml; // preserve for comparison later
|
||||
_textEditorComponent = (builder) =>
|
||||
{
|
||||
CreateTextEditor(builder);
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
|
||||
var interop = new RichTextEditorInterop(JSRuntime);
|
||||
|
||||
if (firstRender)
|
||||
if(_textEditor != null)
|
||||
{
|
||||
await interop.CreateEditor(
|
||||
_editorElement,
|
||||
_toolBar,
|
||||
ReadOnly,
|
||||
Placeholder,
|
||||
Theme,
|
||||
DebugLevel);
|
||||
|
||||
await interop.LoadEditorContent(_editorElement, _richhtml);
|
||||
|
||||
if (AllowRichText)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
_textEditor.Initialize(Content);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
|
||||
public async Task<string> GetHtml()
|
||||
{
|
||||
// evaluate raw html content as first priority
|
||||
if (_rawhtml != _originalrawhtml)
|
||||
return await _textEditor.GetContent();
|
||||
}
|
||||
|
||||
private void CreateTextEditor(RenderTreeBuilder builder)
|
||||
{
|
||||
if(!string.IsNullOrEmpty(_textEditorType))
|
||||
{
|
||||
return _rawhtml;
|
||||
}
|
||||
else
|
||||
{
|
||||
var richhtml = "";
|
||||
if (AllowRichText)
|
||||
var editorType = Type.GetType(_textEditorType);
|
||||
if (editorType != null)
|
||||
{
|
||||
// return rich text content if it has changed
|
||||
var interop = new RichTextEditorInterop(JSRuntime);
|
||||
richhtml = await interop.GetHtml(_editorElement);
|
||||
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
|
||||
{
|
||||
attributes[key] = AdditionalAttributes[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var index = 1;
|
||||
foreach(var name in attributes.Keys)
|
||||
{
|
||||
if (editorType.GetProperty(name) != null)
|
||||
{
|
||||
builder.AddAttribute(index++, name, attributes[name]);
|
||||
}
|
||||
}
|
||||
|
||||
builder.AddComponentReferenceCapture(index, (c) =>
|
||||
{
|
||||
_textEditor = (ITextEditor)c;
|
||||
});
|
||||
builder.CloseComponent();
|
||||
}
|
||||
// rich text value will only be blank if AllowRichText is disabled or the JS Interop method failed
|
||||
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml) && !string.IsNullOrEmpty(_originalrichhtml))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
private string GetTextEditorType()
|
||||
{
|
||||
const string EditorSettingName = "TextEditor";
|
||||
|
||||
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("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();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -36,14 +36,7 @@ else
|
||||
|
||||
Parent.AddTabPanel((TabPanel)this);
|
||||
|
||||
if (string.IsNullOrEmpty(Heading))
|
||||
{
|
||||
Heading = Localize(nameof(Name), Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
Heading = Localize(nameof(Heading), Heading);
|
||||
}
|
||||
Heading = string.IsNullOrEmpty(Heading) ? Localize(nameof(Name), Name) : Localize(nameof(Heading), Heading);
|
||||
}
|
||||
|
||||
public string DisplayHeading()
|
||||
|
@ -23,7 +23,7 @@
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-content @TabContentClass">
|
||||
<br />
|
||||
@ChildContent
|
||||
</div>
|
||||
@ -47,6 +47,9 @@
|
||||
[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)
|
||||
|
||||
[Parameter]
|
||||
public string TabContentClass { get; set; } // optional - to extend the TabContent div.
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
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">
|
||||
@if (_content != null)
|
||||
{
|
||||
<RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" AllowRawHtml="@_allowrawhtml" @ref="@RichTextEditorHtml"></RichTextEditor>
|
||||
<RichTextEditor Content="@_content" @ref="@RichTextEditorHtml"></RichTextEditor>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
@ -47,44 +47,34 @@
|
||||
</TabStrip>
|
||||
|
||||
@code {
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||
|
||||
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 string _content = null;
|
||||
private string _createdby;
|
||||
private DateTime _createdon;
|
||||
private string _modifiedby;
|
||||
private DateTime _modifiedon;
|
||||
private List<Models.HtmlText> _htmltexts;
|
||||
private string _view = "";
|
||||
|
||||
private RichTextEditor RichTextEditorHtml;
|
||||
private bool _allowfilemanagement;
|
||||
private bool _allowrawhtml;
|
||||
private string _content = null;
|
||||
private string _createdby;
|
||||
private DateTime _createdon;
|
||||
private string _modifiedby;
|
||||
private DateTime _modifiedon;
|
||||
private List<Models.HtmlText> _htmltexts;
|
||||
private string _view = "";
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await LoadContent();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Content {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Content.Load"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_allowfilemanagement = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true"));
|
||||
_allowrawhtml = bool.Parse(SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true"));
|
||||
await LoadContent();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Content {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Content.Load"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadContent()
|
||||
{
|
||||
private async Task LoadContent()
|
||||
{
|
||||
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
|
||||
if (htmltext != null)
|
||||
{
|
||||
|
@ -2,30 +2,50 @@
|
||||
@namespace Oqtane.Modules.HtmlText
|
||||
@inherits ModuleBase
|
||||
@inject IHtmlTextService HtmlTextService
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
|
||||
@((MarkupString)content)
|
||||
|
||||
@if (PageState.EditMode)
|
||||
{
|
||||
<br />
|
||||
<ActionLink Action="Edit" EditMode="true" ResourceKey="Edit" />
|
||||
<br />
|
||||
<br />
|
||||
<div class="text-center mb-2">
|
||||
<ActionLink Action="Edit" EditMode="true" ResourceKey="Edit" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@((MarkupString)content)
|
||||
|
||||
@if (PageState.EditMode && content.Length > 3000)
|
||||
{
|
||||
<div class="text-center mt-2">
|
||||
<ActionLink Action="Edit" EditMode="true" ResourceKey="Edit" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private string content = "";
|
||||
private string content = "";
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
try
|
||||
public override string RenderMode => RenderModes.Static;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
|
||||
if (htmltext != null)
|
||||
if (ShouldRender())
|
||||
{
|
||||
content = htmltext.Content;
|
||||
content = Utilities.FormatContent(content, PageState.Alias, "render");
|
||||
var htmltext = await HtmlTextService.GetHtmlTextAsync(ModuleState.ModuleId);
|
||||
if (htmltext != null)
|
||||
{
|
||||
content = htmltext.Content;
|
||||
content = Utilities.FormatContent(content, PageState.Alias, "render");
|
||||
if (bool.Parse(SettingService.GetSetting(ModuleState.Settings, "DynamicTokens", "false")))
|
||||
{
|
||||
content = ReplaceTokens(content);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
content = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Documentation;
|
||||
@ -9,7 +8,7 @@ using Oqtane.Shared;
|
||||
namespace Oqtane.Modules.HtmlText.Services
|
||||
{
|
||||
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||
public class HtmlTextService : ServiceBase, IHtmlTextService, IService
|
||||
public class HtmlTextService : ServiceBase, IHtmlTextService, IClientService
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -13,7 +13,7 @@ namespace Oqtane.Modules.HtmlText.Services
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -5,42 +5,37 @@
|
||||
@inject IStringLocalizer<Settings> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<div class="container">
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<select id="files" class="form-select" @bind="@_allowfilemanagement">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<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="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">
|
||||
<select id="dynamictokens" class="form-select" @bind="@_dynamictokens">
|
||||
<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="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>
|
||||
|
||||
@code {
|
||||
private string resourceType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"; // for localization
|
||||
private string _allowfilemanagement;
|
||||
private string _allowrawhtml;
|
||||
@code {
|
||||
private string resourceType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"; // for localization
|
||||
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
|
||||
private string _dynamictokens;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
try
|
||||
{
|
||||
_allowfilemanagement = SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true");
|
||||
_allowrawhtml = SettingService.GetSetting(ModuleState.Settings, "AllowRawHtml", "true");
|
||||
}
|
||||
_dynamictokens = SettingService.GetSetting(ModuleState.Settings, "DynamicTokens", "false");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,14 +43,13 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||
settings = SettingService.SetSetting(settings, "AllowFileManagement", _allowfilemanagement);
|
||||
settings = SettingService.SetSetting(settings, "AllowRawHtml", _allowrawhtml);
|
||||
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||
settings = SettingService.SetSetting(settings, "DynamicTokens", _dynamictokens);
|
||||
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using System.Collections.Generic;
|
||||
using Microsoft.JSInterop;
|
||||
using System.Linq;
|
||||
using System.Dynamic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Oqtane.Modules
|
||||
{
|
||||
@ -18,6 +19,7 @@ namespace Oqtane.Modules
|
||||
private Logger _logger;
|
||||
private string _urlparametersstate;
|
||||
private Dictionary<string, string> _urlparameters;
|
||||
private bool _scriptsloaded = false;
|
||||
|
||||
protected Logger logger => _logger ?? (_logger = new Logger(this));
|
||||
|
||||
@ -34,10 +36,10 @@ namespace Oqtane.Modules
|
||||
protected PageState PageState { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
protected Module ModuleState { get; set; }
|
||||
protected Models.Module ModuleState { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public ModuleInstance ModuleInstance { get; set; }
|
||||
public RenderModeBoundary RenderModeBoundary { get; set; }
|
||||
|
||||
// optional interface properties
|
||||
public virtual SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.View; } set { } } // default security
|
||||
@ -50,6 +52,10 @@ namespace Oqtane.Modules
|
||||
|
||||
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
|
||||
public virtual string UrlParametersTemplate { get; set; }
|
||||
|
||||
@ -77,7 +83,7 @@ namespace Oqtane.Modules
|
||||
{
|
||||
if (PageState.Page.Resources != null)
|
||||
{
|
||||
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level != ResourceLevel.Site && item.Namespace == type.Namespace).ToList();
|
||||
resources = PageState.Page.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Module && item.Namespace == type.Namespace).ToList();
|
||||
}
|
||||
}
|
||||
else // modulecontrolbase
|
||||
@ -87,22 +93,25 @@ namespace Oqtane.Modules
|
||||
resources = Resources.Where(item => item.ResourceType == ResourceType.Script).ToList();
|
||||
}
|
||||
}
|
||||
if (resources != null &&resources.Any())
|
||||
if (resources != null && resources.Any())
|
||||
{
|
||||
var interop = new Interop(JSRuntime);
|
||||
var scripts = new List<object>();
|
||||
var inline = 0;
|
||||
foreach (Resource resource in resources)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(resource.Url))
|
||||
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive)
|
||||
{
|
||||
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() });
|
||||
}
|
||||
else
|
||||
{
|
||||
inline += 1;
|
||||
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
|
||||
if (!string.IsNullOrEmpty(resource.Url))
|
||||
{
|
||||
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
|
||||
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
|
||||
{
|
||||
inline += 1;
|
||||
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Type ?? "", resource.Content, resource.Location.ToString().ToLower());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scripts.Any())
|
||||
@ -110,6 +119,20 @@ namespace Oqtane.Modules
|
||||
await interop.IncludeScripts(scripts.ToArray());
|
||||
}
|
||||
}
|
||||
_scriptsloaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool ShouldRender()
|
||||
{
|
||||
return PageState?.RenderId == ModuleState?.RenderId;
|
||||
}
|
||||
|
||||
public bool ScriptsLoaded
|
||||
{
|
||||
get
|
||||
{
|
||||
return _scriptsloaded;
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,8 +143,18 @@ namespace Oqtane.Modules
|
||||
return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/";
|
||||
}
|
||||
|
||||
// fingerprint hash code for static assets
|
||||
public string Fingerprint
|
||||
{
|
||||
get
|
||||
{
|
||||
return ModuleState.ModuleDefinition.Fingerprint;
|
||||
}
|
||||
}
|
||||
|
||||
// url methods
|
||||
|
||||
// navigate url
|
||||
public string NavigateUrl()
|
||||
{
|
||||
return NavigateUrl(PageState.Page.Path);
|
||||
@ -137,24 +170,65 @@ namespace Oqtane.Modules
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
@ -162,16 +236,27 @@ namespace Oqtane.Modules
|
||||
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)
|
||||
{
|
||||
return FileUrl(folderpath, filename, false);
|
||||
@ -191,6 +276,8 @@ namespace Oqtane.Modules
|
||||
return Utilities.FileUrl(PageState.Alias, fileid, download);
|
||||
}
|
||||
|
||||
// image url
|
||||
|
||||
public string ImageUrl(int fileid, int width, int height)
|
||||
{
|
||||
return ImageUrl(fileid, width, height, "");
|
||||
@ -266,22 +353,22 @@ namespace Oqtane.Modules
|
||||
|
||||
public void AddModuleMessage(string message, MessageType type, string position)
|
||||
{
|
||||
ModuleInstance.AddModuleMessage(message, type, position);
|
||||
RenderModeBoundary.AddModuleMessage(message, type, position);
|
||||
}
|
||||
|
||||
public void ClearModuleMessage()
|
||||
{
|
||||
ModuleInstance.AddModuleMessage("", MessageType.Undefined);
|
||||
RenderModeBoundary.AddModuleMessage("", MessageType.Undefined);
|
||||
}
|
||||
|
||||
public void ShowProgressIndicator()
|
||||
{
|
||||
ModuleInstance.ShowProgressIndicator();
|
||||
RenderModeBoundary.ShowProgressIndicator();
|
||||
}
|
||||
|
||||
public void HideProgressIndicator()
|
||||
{
|
||||
ModuleInstance.HideProgressIndicator();
|
||||
RenderModeBoundary.HideProgressIndicator();
|
||||
}
|
||||
|
||||
public void SetModuleTitle(string title)
|
||||
@ -327,6 +414,79 @@ namespace Oqtane.Modules
|
||||
await interop.ScrollTo(0, 0, "smooth");
|
||||
}
|
||||
|
||||
public string ReplaceTokens(string content)
|
||||
{
|
||||
return ReplaceTokens(content, null);
|
||||
}
|
||||
|
||||
public string ReplaceTokens(string content, object obj)
|
||||
{
|
||||
var tokens = new List<string>();
|
||||
var pos = content.IndexOf("[");
|
||||
if (pos != -1)
|
||||
{
|
||||
if (content.IndexOf("]", pos) != -1)
|
||||
{
|
||||
var token = content.Substring(pos, content.IndexOf("]", pos) - pos + 1);
|
||||
if (token.Contains(":"))
|
||||
{
|
||||
tokens.Add(token.Substring(1, token.Length - 2));
|
||||
}
|
||||
}
|
||||
pos = content.IndexOf("[", pos + 1);
|
||||
}
|
||||
if (tokens.Count != 0)
|
||||
{
|
||||
foreach (string token in tokens)
|
||||
{
|
||||
var segments = token.Split(":");
|
||||
if (segments.Length >= 2 && segments.Length <= 3)
|
||||
{
|
||||
var objectName = string.Join(":", segments, 0, segments.Length - 1);
|
||||
var propertyName = segments[segments.Length - 1];
|
||||
var propertyValue = "";
|
||||
|
||||
switch (objectName)
|
||||
{
|
||||
case "ModuleState":
|
||||
propertyValue = ModuleState.GetType().GetProperty(propertyName)?.GetValue(ModuleState, null).ToString();
|
||||
break;
|
||||
case "PageState":
|
||||
propertyValue = PageState.GetType().GetProperty(propertyName)?.GetValue(PageState, null).ToString();
|
||||
break;
|
||||
case "PageState:Alias":
|
||||
propertyValue = PageState.Alias.GetType().GetProperty(propertyName)?.GetValue(PageState.Alias, null).ToString();
|
||||
break;
|
||||
case "PageState:Site":
|
||||
propertyValue = PageState.Site.GetType().GetProperty(propertyName)?.GetValue(PageState.Site, null).ToString();
|
||||
break;
|
||||
case "PageState:Page":
|
||||
propertyValue = PageState.Page.GetType().GetProperty(propertyName)?.GetValue(PageState.Page, null).ToString();
|
||||
break;
|
||||
case "PageState:User":
|
||||
propertyValue = PageState.User?.GetType().GetProperty(propertyName)?.GetValue(PageState.User, null).ToString();
|
||||
break;
|
||||
case "PageState:Route":
|
||||
propertyValue = PageState.Route.GetType().GetProperty(propertyName)?.GetValue(PageState.Route, null).ToString();
|
||||
break;
|
||||
default:
|
||||
if (obj != null && obj.GetType().Name == objectName)
|
||||
{
|
||||
propertyValue = obj.GetType().GetProperty(propertyName)?.GetValue(obj, null).ToString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (propertyValue != null)
|
||||
{
|
||||
content = content.Replace("[" + token + "]", propertyValue);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
// logging methods
|
||||
public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args)
|
||||
{
|
||||
@ -481,5 +641,8 @@ namespace Oqtane.Modules
|
||||
{
|
||||
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">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>5.0.1</Version>
|
||||
<Version>6.1.1</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -12,22 +12,20 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.0.1</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.1</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
<IsPackable>true</IsPackable>
|
||||
<BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData>
|
||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
@ -13,13 +12,13 @@ using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.JSInterop;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.UI;
|
||||
|
||||
namespace Oqtane.Client
|
||||
@ -41,10 +40,10 @@ namespace Oqtane.Client
|
||||
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
|
||||
|
||||
// register auth services
|
||||
builder.Services.AddOqtaneAuthorization();
|
||||
builder.Services.AddOqtaneAuthentication();
|
||||
|
||||
// register scoped core services
|
||||
builder.Services.AddOqtaneScopedServices();
|
||||
builder.Services.AddOqtaneClientScopedServices();
|
||||
|
||||
var serviceProvider = builder.Services.BuildServiceProvider();
|
||||
|
||||
@ -220,6 +219,16 @@ namespace Oqtane.Client
|
||||
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
|
||||
{
|
||||
@ -248,7 +257,7 @@ namespace Oqtane.Client
|
||||
var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
|
||||
var interop = new Interop(jsRuntime);
|
||||
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 cultures = await localizationService.GetCulturesAsync(false);
|
||||
|
||||
|
@ -8,20 +8,20 @@
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Oqtane.Client": {
|
||||
"Oqtane": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "http://localhost:44358/"
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Oqtane.Models;
|
||||
@ -16,12 +14,10 @@ namespace Oqtane.Providers
|
||||
public class IdentityAuthenticationStateProvider : AuthenticationStateProvider
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly NavigationManager _navigationManager;
|
||||
|
||||
public IdentityAuthenticationStateProvider(IServiceProvider serviceProvider, NavigationManager navigationManager)
|
||||
public IdentityAuthenticationStateProvider(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_navigationManager = navigationManager;
|
||||
}
|
||||
|
||||
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>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@ -153,7 +153,7 @@
|
||||
<data name="Integrated" xml:space="preserve">
|
||||
<value>Integrated</value>
|
||||
</data>
|
||||
<data name="Encryption,Text" xml:space="preserve">
|
||||
<data name="Encryption.Text" xml:space="preserve">
|
||||
<value>Encryption:</value>
|
||||
</data>
|
||||
<data name="Encryption.HelpText" xml:space="preserve">
|
||||
|
@ -183,4 +183,13 @@
|
||||
<data name="Template" xml:space="preserve">
|
||||
<value>Select a site template</value>
|
||||
</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>
|
@ -150,14 +150,11 @@
|
||||
<data name="Name.HelpText" xml:space="preserve">
|
||||
<value>Enter the folder name</value>
|
||||
</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">
|
||||
<value>Parent: </value>
|
||||
</data>
|
||||
<data name="Permissions.Text" xml:space="preserve">
|
||||
<value>Permissions: </value>
|
||||
<data name="Permissions.Heading" xml:space="preserve">
|
||||
<value>Permissions</value>
|
||||
</data>
|
||||
<data name="DeleteFolder.Header" xml:space="preserve">
|
||||
<value>Delete Folder</value>
|
||||
@ -178,7 +175,7 @@
|
||||
<value>Capacity:</value>
|
||||
</data>
|
||||
<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 name="ImageSizes.Text" xml:space="preserve">
|
||||
<value>Image Sizes:</value>
|
||||
@ -195,4 +192,16 @@
|
||||
<data name="Folder Management" xml:space="preserve">
|
||||
<value>Folder Management</value>
|
||||
</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>
|
@ -165,4 +165,31 @@
|
||||
<data name="UploadFiles.Text" xml:space="preserve">
|
||||
<value>Upload Files</value>
|
||||
</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>
|
@ -192,4 +192,7 @@
|
||||
<data name="Once" xml:space="preserve">
|
||||
<value>Execute Once</value>
|
||||
</data>
|
||||
<data name="Message.StartEndDateError" xml:space="preserve">
|
||||
<value>Start Date cannot be after End Date.</value>
|
||||
</data>
|
||||
</root>
|
@ -144,17 +144,8 @@
|
||||
<data name="Month" xml:space="preserve">
|
||||
<value>Month(s)</value>
|
||||
</data>
|
||||
<data name="Error.Job.Delete" xml:space="preserve">
|
||||
<value>Error Deleting Job</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 name="ViewLogs.Text" xml:space="preserve">
|
||||
<value>View All Logs</value>
|
||||
</data>
|
||||
<data name="Frequency" xml:space="preserve">
|
||||
<value>Frequency</value>
|
||||
@ -165,9 +156,6 @@
|
||||
<data name="Stop" xml:space="preserve">
|
||||
<value>Stop</value>
|
||||
</data>
|
||||
<data name="DeleteJob.Text" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="EditJob.Text" xml:space="preserve">
|
||||
<value>Edit</value>
|
||||
</data>
|
||||
@ -192,9 +180,6 @@
|
||||
<data name="Once" xml:space="preserve">
|
||||
<value>Execute Once</value>
|
||||
</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">
|
||||
<value>Refresh</value>
|
||||
</data>
|
||||
|
@ -132,4 +132,7 @@
|
||||
<data name="Failed" xml:space="preserve">
|
||||
<value>Failed</value>
|
||||
</data>
|
||||
<data name="Refresh" xml:space="preserve">
|
||||
<value>Refresh</value>
|
||||
</data>
|
||||
</root>
|
@ -228,4 +228,7 @@
|
||||
<data name="ExternalLoginStatus.ReviewClaims" xml:space="preserve">
|
||||
<value>The Review Claims Option Was Enabled In External Login Settings. Please Visit The Event Log To View The Claims Returned By The Provider.</value>
|
||||
</data>
|
||||
<data name="Register" xml:space="preserve">
|
||||
<value>Register as new user?</value>
|
||||
</data>
|
||||
</root>
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@ -210,4 +210,16 @@
|
||||
<data name="Success.SaveSiteSettings" xml:space="preserve">
|
||||
<value>Settings Saved Successfully</value>
|
||||
</data>
|
||||
<data name="DeleteLogs.Header" xml:space="preserve">
|
||||
<value>Clear Events</value>
|
||||
</data>
|
||||
<data name="DeleteLogs.Message" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Remove All Log Events?</value>
|
||||
</data>
|
||||
<data name="DeleteLogs.Text" xml:space="preserve">
|
||||
<value>Clear Events</value>
|
||||
</data>
|
||||
<data name="Error.DeleteLogs" xml:space="preserve">
|
||||
<value>Error Deleting Log Events</value>
|
||||
</data>
|
||||
</root>
|
@ -130,16 +130,16 @@
|
||||
<value>Please Note That The Module Creator Is Only Intended To Be Used In A Development Environment</value>
|
||||
</data>
|
||||
<data name="Message.Require.ValidName" xml:space="preserve">
|
||||
<value>You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template</value>
|
||||
<value>You Must Provide A Valid Owner Name And Module Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same Or Contain The Word "Oqtane" ) And Choose A Template</value>
|
||||
</data>
|
||||
<data name="Message.Require.ValidDescription" xml:space="preserve">
|
||||
<value>You Must Provide A Valid Description (ie. No Punctuation)</value>
|
||||
</data>
|
||||
<data name="OwnerName.HelpText" xml:space="preserve">
|
||||
<value>Enter the name of the organization who is developing this module. It should not contain spaces or punctuation.</value>
|
||||
<value>Enter the name of the organization who is developing this module. It should not contain spaces or punctuation or contain the word "oqtane".</value>
|
||||
</data>
|
||||
<data name="ModuleName.HelpText" xml:space="preserve">
|
||||
<value>Enter a name for this module. It should not contain spaces or punctuation.</value>
|
||||
<value>Enter a name for this module. It should not contain spaces or punctuation or contain the word "oqtane".</value>
|
||||
</data>
|
||||
<data name="Description.HelpText" xml:space="preserve">
|
||||
<value>Enter a short description for the module</value>
|
||||
@ -171,4 +171,4 @@
|
||||
<data name="Success.Module.Create" xml:space="preserve">
|
||||
<value>The Source Code For Your Module Has Been Created At The Location Specified Below And Must Be Compiled In Order To Make It Functional. Once It Has Been Compiled You Must <a href={0}>Restart</a> Your Application To Activate The Module.</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
|
@ -228,18 +228,6 @@
|
||||
<data name="View License" xml:space="preserve">
|
||||
<value>View License</value>
|
||||
</data>
|
||||
<data name="Error.Validate" xml:space="preserve">
|
||||
<value>Error Validating Package</value>
|
||||
</data>
|
||||
<data name="Message.Download" xml:space="preserve">
|
||||
<value>Package Version Has Been Verified. Please Select The Download Button To Obtain The Package.</value>
|
||||
</data>
|
||||
<data name="Message.Validate" xml:space="preserve">
|
||||
<value>This Package Version Has Not Been Registered In The Oqtane Marketplace Or You Do Not Have The Right To Use It From This Installation</value>
|
||||
</data>
|
||||
<data name="Validate" xml:space="preserve">
|
||||
<value>Validate</value>
|
||||
</data>
|
||||
<data name="Browse" xml:space="preserve">
|
||||
<value>Browse</value>
|
||||
</data>
|
||||
|
@ -156,7 +156,7 @@
|
||||
<data name="Module.Text" xml:space="preserve">
|
||||
<value>Module:</value>
|
||||
</data>
|
||||
<data name="Module Settings" xml:space="preserve">
|
||||
<data name="ModuleSettings.Heading" xml:space="preserve">
|
||||
<value>Module Settings</value>
|
||||
</data>
|
||||
<data name="Pane.HelpText" xml:space="preserve">
|
||||
@ -165,4 +165,28 @@
|
||||
<data name="Pane.Text" xml:space="preserve">
|
||||
<value>Pane:</value>
|
||||
</data>
|
||||
<data name="EffectiveDate.HelpText" xml:space="preserve">
|
||||
<value>The date that this module is active</value>
|
||||
</data>
|
||||
<data name="EffectiveDate.Text" xml:space="preserve">
|
||||
<value>Effective Date: </value>
|
||||
</data>
|
||||
<data name="ExpiryDate.HelpText" xml:space="preserve">
|
||||
<value>The date that this module expires</value>
|
||||
</data>
|
||||
<data name="ExpiryDate.Text" xml:space="preserve">
|
||||
<value>Expiry Date: </value>
|
||||
</data>
|
||||
<data name="Permissions.Text" xml:space="preserve">
|
||||
<value>Permissions</value>
|
||||
</data>
|
||||
<data name="Permissions.Heading" xml:space="preserve">
|
||||
<value>Permissions</value>
|
||||
</data>
|
||||
<data name="ContainerSettings.Heading" xml:space="preserve">
|
||||
<value>Container Settings</value>
|
||||
</data>
|
||||
<data name="ModuleSettings.Title" xml:space="preserve">
|
||||
<value>Module Settings</value>
|
||||
</data>
|
||||
</root>
|
@ -255,4 +255,16 @@
|
||||
<data name="Theme.Heading" xml:space="preserve">
|
||||
<value>Theme Settings</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="EffectiveDate.HelpText" xml:space="preserve">
|
||||
<value>The date that this page is active</value>
|
||||
</data>
|
||||
<data name="EffectiveDate.Text" xml:space="preserve">
|
||||
<value>Effective Date: </value>
|
||||
</data>
|
||||
<data name="ExpiryDate.HelpText" xml:space="preserve">
|
||||
<value>The date that this page expires</value>
|
||||
</data>
|
||||
<data name="ExpiryDate.Text" xml:space="preserve">
|
||||
<value>Expiry Date: </value>
|
||||
</data>
|
||||
</root>
|
||||
|
@ -169,7 +169,7 @@
|
||||
<value>Select whether the page is part of the site navigation or hidden</value>
|
||||
</data>
|
||||
<data name="UrlPath.HelpText" xml:space="preserve">
|
||||
<value>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 '/'.</value>
|
||||
<value>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 '/'.</value>
|
||||
</data>
|
||||
<data name="Redirect.HelpText" xml:space="preserve">
|
||||
<value>Optionally enter a url which this page should redirect to when a user navigates to it</value>
|
||||
@ -285,4 +285,22 @@
|
||||
<data name="ThemeChanged.Message" xml:space="preserve">
|
||||
<value>Please Note That Overriding The Default Site Theme With An Unrelated Page Theme May Result In Compatibility Issues For Your Site</value>
|
||||
</data>
|
||||
<data name="EffectiveDate.HelpText" xml:space="preserve">
|
||||
<value>The date that this page is active</value>
|
||||
</data>
|
||||
<data name="EffectiveDate.Text" xml:space="preserve">
|
||||
<value>Effective Date: </value>
|
||||
</data>
|
||||
<data name="ExpiryDate.HelpText" xml:space="preserve">
|
||||
<value>The date that this page expires</value>
|
||||
</data>
|
||||
<data name="ExpiryDate.Text" xml:space="preserve">
|
||||
<value>Expiry Date: </value>
|
||||
</data>
|
||||
<data name="PersonalizedUrlPath.Text" xml:space="preserve">
|
||||
<value>Url Path:</value>
|
||||
</data>
|
||||
<data name="PersonalizedUrlPath.HelpText" xml:space="preserve">
|
||||
<value>Provide a url path for your personalized page. Please note that spaces and punctuation will be replaced by a dash.</value>
|
||||
</data>
|
||||
</root>
|
@ -138,4 +138,7 @@
|
||||
<data name="EditPage.Text" xml:space="preserve">
|
||||
<value>Edit</value>
|
||||
</data>
|
||||
<data name="Error.Page.Load" xml:space="preserve">
|
||||
<value>Error Loading Pages</value>
|
||||
</data>
|
||||
</root>
|
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@ -195,4 +195,10 @@
|
||||
<data name="Rows.Text" xml:space="preserve">
|
||||
<value>Rows: </value>
|
||||
</data>
|
||||
<data name="Autocomplete.HelpText" xml:space="preserve">
|
||||
<value>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.</value>
|
||||
</data>
|
||||
<data name="Autocomplete.Text" xml:space="preserve">
|
||||
<value>Autocomplete: </value>
|
||||
</data>
|
||||
</root>
|
||||
|
@ -147,4 +147,7 @@
|
||||
<data name="Title" xml:space="preserve">
|
||||
<value>Title</value>
|
||||
</data>
|
||||
<data name="Detail.Text" xml:space="preserve">
|
||||
<value>Detail</value>
|
||||
</data>
|
||||
</root>
|
@ -195,4 +195,25 @@
|
||||
<data name="Modules.Heading" xml:space="preserve">
|
||||
<value>Modules</value>
|
||||
</data>
|
||||
<data name="Message.Page.Restore" xml:space="preserve">
|
||||
<value>You Cannot Restore A Page If Its Parent Is Deleted</value>
|
||||
</data>
|
||||
<data name="Success.Page.Restore" xml:space="preserve">
|
||||
<value>Page Restored Successfully</value>
|
||||
</data>
|
||||
<data name="Success.Page.Delete" xml:space="preserve">
|
||||
<value>Page Deleted Successfully</value>
|
||||
</data>
|
||||
<data name="Success.Pages.Deleted" xml:space="preserve">
|
||||
<value>All Pages Deleted Successfully</value>
|
||||
</data>
|
||||
<data name="Success.Module.Restore" xml:space="preserve">
|
||||
<value>Module Restored Successfully</value>
|
||||
</data>
|
||||
<data name="Success.Module.Delete" xml:space="preserve">
|
||||
<value>Module Deleted Successfully</value>
|
||||
</data>
|
||||
<data name="Success.Modules.Delete" xml:space="preserve">
|
||||
<value>All Modules Deleted Successfully</value>
|
||||
</data>
|
||||
</root>
|
@ -177,4 +177,7 @@
|
||||
<data name="Username.Text" xml:space="preserve">
|
||||
<value>Username:</value>
|
||||
</data>
|
||||
<data name="Login" xml:space="preserve">
|
||||
<value>Already have account? Login now.</value>
|
||||
</data>
|
||||
</root>
|
180
Oqtane.Client/Resources/Modules/Admin/Search/Index.resx
Normal file
180
Oqtane.Client/Resources/Modules/Admin/Search/Index.resx
Normal file
@ -0,0 +1,180 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Enabled.Text" xml:space="preserve">
|
||||
<value>Enabled? </value>
|
||||
</data>
|
||||
<data name="Enabled.HelpText" xml:space="preserve">
|
||||
<value>Specify if search indexing is enabled</value>
|
||||
</data>
|
||||
<data name="LastIndexedOn.Text" xml:space="preserve">
|
||||
<value>Last Indexed: </value>
|
||||
</data>
|
||||
<data name="LastIndexedOn.HelpText" xml:space="preserve">
|
||||
<value>The date/time which the site was last indexed on</value>
|
||||
</data>
|
||||
<data name="IgnorePages.Text" xml:space="preserve">
|
||||
<value>Ignore Pages: </value>
|
||||
</data>
|
||||
<data name="IgnorePages.HelpText" xml:space="preserve">
|
||||
<value>Comma delimited list of pages which should be ignored (based on page path)</value>
|
||||
</data>
|
||||
<data name="IgnoreEntities.Text" xml:space="preserve">
|
||||
<value>Ignore Entities: </value>
|
||||
</data>
|
||||
<data name="IgnoreEntities.HelpText" xml:space="preserve">
|
||||
<value>Comma delimited list of entities which should be ignored. By default File entities are ignored.</value>
|
||||
</data>
|
||||
<data name="MinimumWordLength.Text" xml:space="preserve">
|
||||
<value>Word Length: </value>
|
||||
</data>
|
||||
<data name="MinimumWordLength.HelpText" xml:space="preserve">
|
||||
<value>Minimum length of a word to be indexed</value>
|
||||
</data>
|
||||
<data name="IgnoreWords.Text" xml:space="preserve">
|
||||
<value>Ignore Words: </value>
|
||||
</data>
|
||||
<data name="IgnoreWords.HelpText" xml:space="preserve">
|
||||
<value>Comma delimited list of words which should be ignored</value>
|
||||
</data>
|
||||
<data name="Success.Save" xml:space="preserve">
|
||||
<value>Search Settings Saved Successfully. You Will Need Reindex For Your Changes To Be Reflected In The Search Results.</value>
|
||||
</data>
|
||||
<data name="Error.Save" xml:space="preserve">
|
||||
<value>Error Saving Search Settings</value>
|
||||
</data>
|
||||
<data name="SearchProvider.HelpText" xml:space="preserve">
|
||||
<value>Specify the search provider for this site</value>
|
||||
</data>
|
||||
<data name="SearchProvider.Text" xml:space="preserve">
|
||||
<value>Search Provider:</value>
|
||||
</data>
|
||||
<data name="Message.Reindex" xml:space="preserve">
|
||||
<value>The search index will be rebuilt for this site. Please be patient during the reindexing process.</value>
|
||||
</data>
|
||||
<data name="Reindex.Text" xml:space="preserve">
|
||||
<value>Reindex</value>
|
||||
</data>
|
||||
<data name="Reindex.Header" xml:space="preserve">
|
||||
<value>Reindex</value>
|
||||
</data>
|
||||
<data name="Reindex.Message" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Reindex Search Content?</value>
|
||||
</data>
|
||||
</root>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user