Compare commits
3851 Commits
v3.1.2
...
b3b39f583a
| Author | SHA1 | Date | |
|---|---|---|---|
| b3b39f583a | |||
| 92c554e854 | |||
|
|
29fe3dfd0b | ||
|
|
985e50d415 | ||
|
|
11150b6a10 | ||
|
|
c499acdc4a | ||
|
|
b24e3252d9 | ||
|
|
e15787b1e4 | ||
|
|
d5f19d97e2 | ||
|
|
5543a4aeed | ||
|
|
9c333232e2 | ||
|
|
d52b95ea23 | ||
|
|
ef4fbcbb8a | ||
|
|
aa454b411f | ||
|
|
543e9339c7 | ||
|
|
7fff5c0d18 | ||
|
|
fa79f3f6fa | ||
|
|
c098839881 | ||
|
|
8d0d88c1b9 | ||
|
|
2b6ba0f410 | ||
|
|
11235009c0 | ||
|
|
4b05f7fdad | ||
|
|
338b0ae509 | ||
|
|
a437082952 | ||
|
|
ca9aba7b3b | ||
|
|
fe9f189734 | ||
|
|
018ac612f4 | ||
|
|
5bde40ec2b | ||
|
|
4d5780c192 | ||
|
|
ff6a810ad5 | ||
|
|
feec01ba00 | ||
|
|
1f05d12ef5 | ||
|
|
31aba14507 | ||
|
|
bbd6f13f36 | ||
|
|
68edbbbdb9 | ||
|
|
eb5a0dc1c9 | ||
|
|
7cea4f1792 | ||
|
|
c57c6abb1b | ||
|
|
a25b706c7b | ||
|
|
5d077e843d | ||
|
|
65bf3e9899 | ||
|
|
51ba3a01f5 | ||
|
|
f9ca611b8b | ||
|
|
a49b8728fd | ||
|
|
9234b91089 | ||
|
|
f3fcef52dd | ||
|
|
7f8b741981 | ||
|
|
f1791a709c | ||
|
|
30307fb05e | ||
|
|
57d443be8d | ||
|
|
84844c5043 | ||
|
|
9000f05961 | ||
|
|
6bef9b5c6e | ||
|
|
ffef1f4820 | ||
|
|
10c7bdbcaa | ||
|
|
e8f9888a41 | ||
|
|
8bac702be6 | ||
|
|
128bcecfe3 | ||
|
|
1390b3c489 | ||
|
|
a0f41341ac | ||
|
|
8ffa7ef7ff | ||
|
|
deb4607081 | ||
|
|
49c62f80e8 | ||
|
|
139793f3c0 | ||
|
|
f237cb9655 | ||
|
|
9f18c460d8 | ||
|
|
306a41b442 | ||
|
|
b53f54295d | ||
|
|
7e4f066694 | ||
|
|
90d72489d9 | ||
|
|
045c455324 | ||
|
|
60da903360 | ||
|
|
6d2a71f37e | ||
|
|
c8f60a12a4 | ||
|
|
11b2d3aa43 | ||
|
|
3d9c81d850 | ||
|
|
8ccb1a24f8 | ||
|
|
48c6796128 | ||
|
|
fc403f920b | ||
|
|
ca19496b5c | ||
|
|
0ae38f8a40 | ||
|
|
ad868ba841 | ||
|
|
f2aa39aa85 | ||
|
|
e76e0fc351 | ||
|
|
a348913888 | ||
|
|
5507006c53 | ||
|
|
994429f098 | ||
|
|
8efdcb9c49 | ||
|
|
db9a40db2b | ||
|
|
25667499e6 | ||
|
|
a728cd2d91 | ||
|
|
3f5f3ef10b | ||
|
|
0d708124c2 | ||
|
|
d31f73df14 | ||
|
|
3fed45438b | ||
|
|
8aa967fa1b | ||
|
|
6eaa3e342c | ||
|
|
6fc9e60f62 | ||
|
|
c39ffcf51c | ||
|
|
6f60a91f4c | ||
|
|
8031df6f28 | ||
|
|
da1e859fda | ||
|
|
8d4d25f1d1 | ||
|
|
6aff27778d | ||
|
|
ca2dcbfec0 | ||
|
|
24b666a382 | ||
|
|
9e34295529 | ||
|
|
10c55d056b | ||
|
|
753ab3bdd7 | ||
|
|
feee8def6f | ||
|
|
c208f12f8c | ||
|
|
dc926bf838 | ||
|
|
55a76b5204 | ||
|
|
53b837e763 | ||
|
|
3a551cdf25 | ||
|
|
c0c7f87dc9 | ||
|
|
ac77fd138b | ||
|
|
30d6e9d67c | ||
|
|
47db4334a1 | ||
|
|
fce97179e1 | ||
|
|
4f16cd2d01 | ||
|
|
fa587691b1 | ||
|
|
e0044658f9 | ||
|
|
53de1ddb36 | ||
|
|
da7b046092 | ||
|
|
d888d83a98 | ||
|
|
430f83e8e9 | ||
|
|
e7acd14faa | ||
|
|
1b00fa74bc | ||
|
|
4d572d8173 | ||
|
|
dbda85d8d9 | ||
|
|
95cb5dd66c | ||
|
|
f64e1c3a6a | ||
|
|
26a686c412 | ||
|
|
2505383f53 | ||
|
|
1a1e9ac6be | ||
|
|
7f20a3179e | ||
|
|
46431f0187 | ||
|
|
1ff8ec78c9 | ||
|
|
7840230c62 | ||
|
|
523db0a005 | ||
|
|
cc906d49ba | ||
|
|
fc23af89d3 | ||
|
|
5d8829ba63 | ||
|
|
31ccd80894 | ||
|
|
bac2234616 | ||
|
|
bd61db76a3 | ||
|
|
bc99e3b992 | ||
|
|
b7314b0813 | ||
|
|
4759bd569f | ||
|
|
b88c28f864 | ||
|
|
774ccb05f8 | ||
|
|
0ac48cba34 | ||
|
|
e36880fe3a | ||
|
|
713cf5de2c | ||
|
|
0fa336411f | ||
|
|
8ebdb09d68 | ||
|
|
40bc53001e | ||
|
|
b1656d1eea | ||
|
|
7aa54bf979 | ||
|
|
231f9bca84 | ||
|
|
e4f8596c19 | ||
|
|
020b7233d0 | ||
|
|
85fc0b3e2f | ||
|
|
5dcc7c14f3 | ||
|
|
7993d27b11 | ||
|
|
1f8c54ce74 | ||
|
|
73a414a34b | ||
|
|
8fa19c4a51 | ||
|
|
0667ae3e15 | ||
|
|
db1d00cd07 | ||
|
|
b27f092bef | ||
|
|
4eaea8e586 | ||
|
|
89cd7d3bbb | ||
|
|
2fff1d8d21 | ||
|
|
850631f00e | ||
|
|
1cea8846cf | ||
|
|
af48a48559 | ||
|
|
655c1762aa | ||
|
|
f706ccfd87 | ||
|
|
71e4c7f117 | ||
|
|
ad6182f4bd | ||
|
|
86bf0f65b0 | ||
|
|
7742f7747d | ||
|
|
eb998c41f2 | ||
|
|
657bd7c97c | ||
|
|
c8286148c1 | ||
|
|
e6ba2cce62 | ||
|
|
6105ff44b4 | ||
|
|
72da77be01 | ||
|
|
4c29b31f1b | ||
|
|
6e640108ed | ||
|
|
157322441d | ||
|
|
61d967e6af | ||
|
|
99f2158e55 | ||
|
|
1cba78cc4e | ||
|
|
1770c1ee11 | ||
|
|
a57fbea0cc | ||
|
|
f0c27c83f1 | ||
|
|
7873ca564c | ||
|
|
5ebc1fec24 | ||
|
|
f2559b7d4d | ||
|
|
1ee92a248e | ||
|
|
8376f98f21 | ||
|
|
810a3e0171 | ||
|
|
2eac9c3795 | ||
|
|
75f2425668 | ||
|
|
2dd1d7e926 | ||
|
|
5bb05a0a51 | ||
|
|
bc2c5b00c6 | ||
|
|
09f5e158dd | ||
|
|
4656471a0a | ||
|
|
69d58a4273 | ||
|
|
53a27677d4 | ||
|
|
f243ad0348 | ||
|
|
b4ce6bbb42 | ||
|
|
fa32937045 | ||
|
|
812e5f3c8e | ||
|
|
e2981e802c | ||
|
|
4ae4705c73 | ||
|
|
fbf4b12713 | ||
|
|
e4ece3e0dc | ||
|
|
9f231421be | ||
| 391827222e | |||
| c1721bd1a1 | |||
| f6630ae241 | |||
| 424cab64a8 | |||
|
|
b4fdbb5e48 | ||
|
|
fbf62ca30d | ||
|
|
05d2096fb8 | ||
|
|
7683af81bc | ||
|
|
bad10b3812 | ||
|
|
9f9522c2ed | ||
|
|
62879c3e52 | ||
|
|
45610f8dd7 | ||
|
|
18102cbd78 | ||
|
|
262d6a1529 | ||
|
|
1124ddaf90 | ||
|
|
981add3872 | ||
|
|
8d4b30140e | ||
|
|
b9c59137a8 | ||
|
|
0b1c7e06ca | ||
|
|
fcaf80cba6 | ||
|
|
6358b9eabb | ||
|
|
70a3fab1ff | ||
|
|
bdf86ace86 | ||
|
|
d57132d1e4 | ||
|
|
a6e87abf99 | ||
|
|
f1771610fe | ||
|
|
a88ea9780f | ||
|
|
bca0866d72 | ||
|
|
cebed93abf | ||
|
|
ee2b2e3569 | ||
|
|
cb8e9ee244 | ||
|
|
486184b16c | ||
|
|
9f9bd1988f | ||
|
|
ba1bfd1bc0 | ||
|
|
e12926e971 | ||
|
|
5b4db0de3b | ||
|
|
70ff55faa6 | ||
|
|
f2bd47d8bc | ||
|
|
e2b9c9e98e | ||
|
|
b0791a594f | ||
|
|
ea5eaa6ed2 | ||
|
|
ec3fd1d585 | ||
|
|
d76de22977 | ||
|
|
c0e3483cc7 | ||
|
|
0994cdf3b6 | ||
|
|
a76fd82262 | ||
|
|
2f919c7d69 | ||
|
|
f12592731b | ||
|
|
4a20e1a25d | ||
|
|
cc7111c3ff | ||
|
|
81972aed62 | ||
|
|
5e2092c6d4 | ||
|
|
d136f8ac91 | ||
|
|
5b23917940 | ||
|
|
2cda0a3798 | ||
|
|
f315ad1ce9 | ||
|
|
49f1c273c2 | ||
|
|
8518476c87 | ||
|
|
a34ed756db | ||
|
|
a48232c4e3 | ||
|
|
ac65e38390 | ||
|
|
eab3a753f5 | ||
|
|
9e5922e121 | ||
|
|
bf57b23776 | ||
|
|
8f4a20fd46 | ||
|
|
b3716da5ac | ||
|
|
2c129fd800 | ||
|
|
6fb18e7a25 | ||
|
|
38d28d6944 | ||
|
|
a187e1a7a2 | ||
|
|
7d7a19c7c2 | ||
|
|
6a2ae2153a | ||
|
|
f50ba1a91e | ||
|
|
b09575dbd6 | ||
|
|
c52ee3d91d | ||
|
|
1ced5c0425 | ||
|
|
e399a5c9b1 | ||
|
|
08dff5fb67 | ||
|
|
912760f2a7 | ||
|
|
4b62fdbf93 | ||
|
|
df593d43a7 | ||
|
|
89b1fba771 | ||
|
|
5505c91ae0 | ||
|
|
cc720ff399 | ||
|
|
29f07f6c56 | ||
|
|
a69e197a1f | ||
|
|
6dddd8eff8 | ||
|
|
51aada8922 | ||
|
|
b47bf40e8f | ||
|
|
48151bf365 | ||
|
|
659950996d | ||
|
|
6e656a4d0a | ||
|
|
bf308dd13d | ||
|
|
982f3b1943 | ||
|
|
7a4ea8cf1b | ||
|
|
7c0482a87c | ||
|
|
101ededd89 | ||
|
|
a8cbc0040e | ||
|
|
ed91bb445b | ||
|
|
f158a222f4 | ||
|
|
46bcad1fca | ||
|
|
5e147afb9f | ||
|
|
b061d4593f | ||
|
|
3fa520b4ef | ||
|
|
2df05b4afd | ||
|
|
e0569a6748 | ||
|
|
2e6ab398d9 | ||
|
|
94b03d2a6b | ||
|
|
f84fe30bb6 | ||
|
|
049ddef531 | ||
|
|
a1a214c742 | ||
|
|
c40a483ffa | ||
|
|
aff99acfae | ||
|
|
628129c08d | ||
|
|
679d34dfdf | ||
|
|
b2f65903ae | ||
|
|
2daefe0382 | ||
|
|
1214a11704 | ||
|
|
e55e0118c2 | ||
|
|
a1ac81e907 | ||
|
|
14ad68bf69 | ||
|
|
e3118c6e99 | ||
|
|
b41aeab8f8 | ||
|
|
1a738b358e | ||
|
|
f4b1e8035b | ||
|
|
324e985247 | ||
|
|
60faacd7d0 | ||
|
|
d4a4d7c346 | ||
|
|
189f8f1d27 | ||
|
|
ed353461da | ||
|
|
e9330d6c62 | ||
|
|
f53e7cc3f6 | ||
|
|
4f4258d532 | ||
|
|
c80910f355 | ||
|
|
12470ab178 | ||
|
|
704e091e9b | ||
|
|
f30f1e5c1f | ||
|
|
0741ce2197 | ||
|
|
fc81bae9b7 | ||
|
|
3fa68e4f96 | ||
|
|
05a767c7be | ||
|
|
8c1e8f6377 | ||
|
|
0fbae8d7da | ||
|
|
cec4b339f5 | ||
|
|
1a7656d8ee | ||
|
|
e173815810 | ||
|
|
620c768e05 | ||
|
|
7740679077 | ||
|
|
1ebc8ebff3 | ||
|
|
fa4fac70d5 | ||
|
|
8c83a18f93 | ||
|
|
a151ecfda0 | ||
|
|
dec0c0649c | ||
|
|
a356f893ac | ||
|
|
e2af4f74c3 | ||
|
|
99022b76e5 | ||
|
|
9dd6dc7523 | ||
|
|
6f588200d7 | ||
|
|
f3dbeae28e | ||
|
|
9f70361298 | ||
|
|
534353ce13 | ||
|
|
3f391a7354 | ||
|
|
0dd0752710 | ||
|
|
710fae4b0e | ||
|
|
de6c57a7ee | ||
|
|
7eee1fcd6a | ||
|
|
1fd2aedf96 | ||
|
|
ffdd7c063b | ||
|
|
a87af264eb | ||
|
|
3640cd2fdd | ||
|
|
f7cf25c4bb | ||
|
|
77dbd0d4c7 | ||
|
|
5a77c83e68 | ||
|
|
0a6763e08c | ||
|
|
b5aa206670 | ||
|
|
dbb4d9b64b | ||
|
|
6775edfd66 | ||
|
|
b06750ed65 | ||
|
|
57879c1891 | ||
|
|
65b55a76f2 | ||
|
|
8562a68306 | ||
|
|
160da46b5a | ||
|
|
ae5f70a739 | ||
|
|
4456e57466 | ||
|
|
24cd090c61 | ||
|
|
4f849f5d5f | ||
|
|
db2e86e84c | ||
|
|
14748ce2b3 | ||
|
|
527509732c | ||
|
|
aa9214477c | ||
|
|
db24ed8b55 | ||
|
|
349d1849d9 | ||
|
|
188be2fa8c | ||
|
|
5c4d7df734 | ||
|
|
37de18c670 | ||
|
|
0001e3844b | ||
|
|
65f171f701 | ||
|
|
c4308c239c | ||
|
|
2b6af3cb37 | ||
|
|
c5a16fbbc1 | ||
|
|
1db83f509b | ||
|
|
2a06304a2c | ||
|
|
7bbe684135 | ||
|
|
a996a88fc4 | ||
|
|
8cf9e7db51 | ||
|
|
ed981c67b7 | ||
|
|
6a77a0a5b9 | ||
|
|
bfb4b4431b | ||
|
|
3de44c0335 | ||
|
|
37afd1aec9 | ||
|
|
b2ac561673 | ||
|
|
c9bf7d9138 | ||
|
|
153a689bdb | ||
|
|
26b88f1a22 | ||
|
|
c66a5d028f | ||
|
|
8441c95a5c | ||
|
|
e0e32b0199 | ||
|
|
2bb76564e9 | ||
|
|
86ec25d4de | ||
|
|
ed9929963c | ||
|
|
36f50118ac | ||
|
|
72ddf27504 | ||
|
|
9bd36931ff | ||
|
|
056ef7a3d5 | ||
|
|
e483945d05 | ||
|
|
7a9c637e03 | ||
|
|
09ce543ea6 | ||
|
|
0ef24ebc3f | ||
|
|
a4419d3af6 | ||
|
|
935983c02a | ||
|
|
bd54ce5017 | ||
|
|
af6ed78b8e | ||
|
|
f72438996d | ||
|
|
9db2a55a5a | ||
|
|
950d90badb | ||
|
|
1864d180af | ||
|
|
0e82e98382 | ||
|
|
46023d35dc | ||
|
|
90d2e0a40b | ||
|
|
5f884e0796 | ||
|
|
16477052e2 | ||
|
|
66a05603f7 | ||
|
|
fe2a883386 | ||
|
|
ca7fdaa125 | ||
|
|
1283ec2008 | ||
|
|
d1f78f9048 | ||
|
|
45f43bfade | ||
|
|
4793ab4bc9 | ||
|
|
88b174dea8 | ||
|
|
06ca382bd7 | ||
|
|
b09175a8db | ||
|
|
677f68b08d | ||
|
|
8058b8dba4 | ||
|
|
4bc26f13c1 | ||
|
|
e6cf77e724 | ||
|
|
f8737c112e | ||
|
|
74b72ed9d4 | ||
|
|
4950391201 | ||
|
|
64a38d6e45 | ||
|
|
e2d8ee53f8 | ||
|
|
0204ff8dd5 | ||
|
|
4630ee6e93 | ||
|
|
c4f2abf143 | ||
|
|
e7444a0194 | ||
|
|
ffed7305ed | ||
|
|
334054bcd4 | ||
|
|
5bb98eb5b2 | ||
|
|
e842bd882a | ||
|
|
74bfb46f73 | ||
|
|
cd45bf4b70 | ||
|
|
51600bbcb0 | ||
|
|
8811a9bcaa | ||
|
|
4521f8a774 | ||
|
|
5b427783f8 | ||
|
|
9508983b15 | ||
|
|
fd09912cd7 | ||
|
|
01cc8584b6 | ||
|
|
c0b104e7c8 | ||
|
|
9a82021a82 | ||
|
|
1fb54a0b0f | ||
|
|
aa5ea61638 | ||
|
|
a59ec0258b | ||
|
|
b403f5cf71 | ||
|
|
0ac6a62b86 | ||
|
|
ed3743d3b6 | ||
|
|
3468cba000 | ||
|
|
5a4cdc5354 | ||
|
|
af4e19a57e | ||
|
|
26bb743679 | ||
|
|
96cc726e22 | ||
|
|
f4b00b01d0 | ||
|
|
127b2ca86d | ||
|
|
4b8b93e1b8 | ||
|
|
3aea412fe9 | ||
|
|
2aef96ad4f | ||
|
|
ec0a77230c | ||
|
|
b35e4bddd0 | ||
|
|
aa32beb341 | ||
|
|
efafe89b42 | ||
|
|
5ef2e49d9c | ||
|
|
1cfbf61a30 | ||
|
|
2bb5494b84 | ||
|
|
e8a41ccb47 | ||
|
|
7184f7f635 | ||
|
|
cc5727b7fa | ||
|
|
7f3d6ef6a5 | ||
|
|
44ce68097b | ||
|
|
d976cc6c19 | ||
|
|
d19d7d2a43 | ||
|
|
9bfaa02f97 | ||
|
|
2d9396b245 | ||
|
|
56e0da64ee | ||
|
|
997e9213f2 | ||
|
|
366569a23b | ||
|
|
36d5747b4f | ||
|
|
ea026c726c | ||
|
|
1e71e32c74 | ||
|
|
ed729bbd4f | ||
|
|
1a925221b7 | ||
|
|
af7b4db062 | ||
|
|
cfefe35e3f | ||
|
|
8d7845a44d | ||
|
|
3a15e6e5e9 | ||
|
|
3b8a51e855 | ||
|
|
f2cb34cc35 | ||
|
|
723ce62a34 | ||
|
|
2c9a2ea021 | ||
|
|
2be008d6d1 | ||
|
|
7fb51bdd0a | ||
|
|
abdbe3694f | ||
|
|
bd87e5012f | ||
|
|
55e18f2364 | ||
|
|
655e84072d | ||
|
|
ab5409d5b6 | ||
|
|
5a5da6486c | ||
|
|
7e99252429 | ||
|
|
10e0dcef8b | ||
|
|
80c8433aad | ||
|
|
5b0ae372f8 | ||
|
|
b5a1b529ab | ||
|
|
af821dcd9a | ||
|
|
10d3c81520 | ||
|
|
e3811b453a | ||
|
|
ca0fb05baa | ||
|
|
2b4b01bf6e | ||
|
|
3a1244bddc | ||
|
|
b8fd922b19 | ||
|
|
03f856025e | ||
|
|
45f04d24c3 | ||
|
|
2435d610c7 | ||
|
|
06572bcd14 | ||
|
|
3fab79afc0 | ||
|
|
2b7dd3fed5 | ||
|
|
d65efed032 | ||
|
|
c6896ea936 | ||
|
|
b7a41bddec | ||
|
|
6fd80c3737 | ||
|
|
0aa690b3b1 | ||
|
|
6d5bcfc6ed | ||
|
|
b60de69fa5 | ||
|
|
d991b57d08 | ||
|
|
1133d7fcba | ||
|
|
6fbf0383bb | ||
|
|
0296230219 | ||
|
|
dedfbba27a | ||
|
|
dc5441da07 | ||
|
|
585648b7f3 | ||
|
|
cd0ee1c26d | ||
|
|
d7a7be5af4 | ||
|
|
13e4267c11 | ||
|
|
15bc47e3e8 | ||
|
|
1a4380dcd7 | ||
|
|
5ace34b5cd | ||
|
|
f010c0f1fa | ||
|
|
2c721ad5dd | ||
|
|
8a7c2ce2c2 | ||
|
|
b2a7b813de | ||
|
|
e85cf04b99 | ||
|
|
ab6fa48172 | ||
|
|
c81905882f | ||
|
|
f0d31c1114 | ||
|
|
497b255216 | ||
|
|
d96286d771 | ||
|
|
2441647d75 | ||
|
|
77b780d631 | ||
|
|
cdd03bf3d4 | ||
|
|
e786c35f7d | ||
|
|
e83399acb1 | ||
|
|
ffea9e3210 | ||
|
|
f71a3a1ce3 | ||
|
|
a5ccc23604 | ||
|
|
1ed4c8a094 | ||
|
|
4a74549c1b | ||
|
|
a499cfb98f | ||
|
|
01038c8296 | ||
|
|
7407f79b3d | ||
|
|
a845dd1976 | ||
|
|
9d7549da70 | ||
|
|
f5cc61384f | ||
|
|
844778d36a | ||
|
|
871b0a274e | ||
|
|
737740a3ca | ||
|
|
ae8d600600 | ||
|
|
2f1691bfb0 | ||
|
|
a3d25f91c8 | ||
|
|
ff84b50817 | ||
|
|
0be8242284 | ||
|
|
e25a6259ea | ||
|
|
1578f82efb | ||
|
|
b5f75f0c5e | ||
|
|
601caab3b6 | ||
|
|
6d3092f440 | ||
|
|
ef27937c7a | ||
|
|
f4a7b79c4f | ||
|
|
2531776a48 | ||
|
|
ced80419aa | ||
|
|
ad2816f4e8 | ||
|
|
3528b8c674 | ||
|
|
80c83c626d | ||
|
|
6a355f2aea | ||
|
|
7d94e4a53a | ||
|
|
823c04742e | ||
|
|
043fb1abd1 | ||
|
|
f01e85b690 | ||
|
|
7eb1298847 | ||
|
|
a5480c9a96 | ||
|
|
f948600e86 | ||
|
|
420182b9bf | ||
|
|
8c430ce1a6 | ||
|
|
d3717dbe19 | ||
|
|
caa83d769f | ||
|
|
365f87828f | ||
|
|
f780887866 | ||
|
|
43627d4bb8 | ||
|
|
5d7b276cd1 | ||
|
|
23597eb997 | ||
|
|
b6948367f8 | ||
|
|
db6dd5abee | ||
|
|
702eb9e466 | ||
|
|
3c99006226 | ||
|
|
aaaf5683a5 | ||
|
|
92aa2236c0 | ||
|
|
d2592f72d6 | ||
|
|
27120d6cc9 | ||
|
|
a2669d35c3 | ||
|
|
574164081b | ||
|
|
00c2f8dcd8 | ||
|
|
0202bf60e5 | ||
|
|
16436a171b | ||
|
|
4cf4e0eabd | ||
|
|
5edb98dfb4 | ||
|
|
899bf22e15 | ||
|
|
9a33167a6c | ||
|
|
2dc068aa21 | ||
|
|
0f2aa4d2e1 | ||
|
|
a699f5c7bc | ||
|
|
ab807de3c0 | ||
|
|
68514bcb36 | ||
|
|
422bf8da59 | ||
|
|
de92dc93dd | ||
|
|
5a91b143b6 | ||
|
|
c745e85706 | ||
|
|
0f698e0c50 | ||
|
|
cd16d77bf0 | ||
|
|
fdbdd0ef4c | ||
|
|
71485f4a82 | ||
|
|
013056a6e5 | ||
|
|
263498fbdc | ||
|
|
f46ac2c007 | ||
|
|
c23841b6db | ||
|
|
18b1b5fca5 | ||
|
|
6707ac2697 | ||
|
|
d85a2fc8ce | ||
|
|
a8997e8f17 | ||
|
|
c8a22d9537 | ||
|
|
910669f786 | ||
|
|
89312c6796 | ||
|
|
a6aa96fdb0 | ||
|
|
7deb0b06af | ||
|
|
784f3771b3 | ||
|
|
80316824f7 | ||
|
|
cbaebb7b7c | ||
|
|
97d3764b6e | ||
|
|
002f8117cb | ||
|
|
0dfdb12431 | ||
|
|
d77e898929 | ||
|
|
4c937be884 | ||
|
|
c25cce4398 | ||
|
|
58c422285a | ||
|
|
f2f22f35e8 | ||
|
|
15867a7807 | ||
|
|
22e3161a9b | ||
|
|
b0c8203b24 | ||
|
|
e2c404d8bb | ||
|
|
5ee1731c92 | ||
|
|
06e8d3b660 | ||
|
|
f09709aedb | ||
|
|
598d5decac | ||
|
|
7832a6053e | ||
|
|
588748230e | ||
|
|
8668165c72 | ||
|
|
a967332f89 | ||
|
|
6719d242bd | ||
|
|
3565185808 | ||
|
|
ce51262197 | ||
|
|
992a786c2b | ||
|
|
038df95aa0 | ||
|
|
4ebd660de2 | ||
|
|
a9ea41a488 | ||
|
|
196594b490 | ||
|
|
1516d5af10 | ||
|
|
ffcd1595a9 | ||
|
|
42e5c6e111 | ||
|
|
8a9651dc50 | ||
|
|
4be2f4f2d9 | ||
|
|
369bf7a235 | ||
|
|
136545b404 | ||
|
|
73ea17ae0f | ||
|
|
23010acef4 | ||
|
|
4f74962ce2 | ||
|
|
7f978c7845 | ||
|
|
731fd46ea2 | ||
|
|
859759d691 | ||
|
|
5e9567158f | ||
|
|
51d244f3aa | ||
|
|
4c5a07edd5 | ||
|
|
8113a754a1 | ||
|
|
3cd40c6195 | ||
|
|
56cfb2ce06 | ||
|
|
72087823ac | ||
|
|
bcf7866fe2 | ||
|
|
b64772e484 | ||
|
|
088d665942 | ||
|
|
7d6c10befb | ||
|
|
f88e3d04b8 | ||
|
|
f57676a22b | ||
|
|
8618cb62e4 | ||
|
|
c31c88ed1f | ||
|
|
6022acd21f | ||
|
|
b3071b9272 | ||
|
|
ec6a6d6653 | ||
|
|
52f552b4de | ||
|
|
2643d3396b | ||
|
|
62d59a09cf | ||
|
|
a68ff8a4f0 | ||
|
|
93d4bfcd7a | ||
|
|
5fb80c1a7b | ||
|
|
04b38444ce | ||
|
|
ca3df02002 | ||
|
|
d952c33fab | ||
|
|
93bc1cd5af | ||
|
|
0e5b370ee8 | ||
|
|
f4fd4e28c9 | ||
|
|
ec8433eb45 | ||
|
|
0d4a40e9bb | ||
|
|
c3668f4179 | ||
|
|
3adb7ecb1c | ||
|
|
aa5b84a214 | ||
|
|
3d83fccbf1 | ||
|
|
4c4255be6b | ||
|
|
ed6054b082 | ||
|
|
f60f7a4dc1 | ||
|
|
998dc95cb2 | ||
|
|
12f5d7b846 | ||
|
|
906ae0a43e | ||
|
|
485b774876 | ||
|
|
3121cf5b75 | ||
|
|
ce7570dae2 | ||
|
|
b5ea0dfbc7 | ||
|
|
dd0f8f4772 | ||
|
|
9d0ab34274 | ||
|
|
e6038be6f7 | ||
|
|
bd2153a0ed | ||
|
|
e526deac20 | ||
|
|
b65f165dcf | ||
|
|
f9fbe5adc2 | ||
|
|
d1e73571a1 | ||
|
|
1047058676 | ||
|
|
29a1e77da8 | ||
|
|
290547e482 | ||
|
|
3df45ca20f | ||
|
|
cc06258484 | ||
|
|
2c262d0655 | ||
|
|
1875e1e158 | ||
|
|
1c95967b31 | ||
|
|
2ae98929de | ||
|
|
6a1dd38cbb | ||
|
|
c458a77d27 | ||
|
|
352c23f389 | ||
|
|
6c5a1dc2e1 | ||
|
|
004ff1e91d | ||
|
|
f7de4c567b | ||
|
|
b2ad1010ac | ||
|
|
660e164ff8 | ||
|
|
eb6dc80b50 | ||
|
|
a9882cc96a | ||
|
|
fb5a2ce178 | ||
|
|
d441b31dc7 | ||
|
|
d4239fe7e0 | ||
|
|
6a98999105 | ||
|
|
5caa1fe7d4 | ||
|
|
ef33bdb65e | ||
|
|
14a463382b | ||
|
|
1ad79874c8 | ||
|
|
1a61a58d28 | ||
|
|
3e3c973679 | ||
|
|
9dede84d20 | ||
|
|
e78b11cf62 | ||
|
|
341ca5a330 | ||
|
|
e5d8c02def | ||
|
|
947aa08c42 | ||
|
|
9a04d436bd | ||
|
|
be0754f568 | ||
|
|
93c4bbc0d1 | ||
|
|
b98535810b | ||
|
|
393cf8da1f | ||
|
|
ea2846973a | ||
|
|
3398c1cbfe | ||
|
|
39c79ea68e | ||
|
|
66900f4a32 | ||
|
|
df71dd14f7 | ||
|
|
8113ca3069 | ||
|
|
d468e675c2 | ||
|
|
1e84cedf82 | ||
|
|
7f4087e3de | ||
|
|
facd3c8956 | ||
|
|
3e50deecb7 | ||
|
|
628c504f84 | ||
|
|
e1ada78c1f | ||
|
|
09fa1e365c | ||
|
|
28b6b03d06 | ||
|
|
a4395b62ff | ||
|
|
4511acf273 | ||
|
|
fde53a2d83 | ||
|
|
f6cd04fdb8 | ||
|
|
a5eede6c7a | ||
|
|
2e83817c83 | ||
|
|
82aea40ae4 | ||
|
|
1b289eae24 | ||
|
|
81420b2c88 | ||
|
|
775731b745 | ||
|
|
489e7d4a67 | ||
|
|
b6508764d8 | ||
|
|
6dedd87305 | ||
|
|
89fa29b310 | ||
|
|
be5df9c22a | ||
|
|
db17739716 | ||
|
|
1439362a5e | ||
|
|
65c1b04772 | ||
|
|
83d30ebdc4 | ||
|
|
b7928a5255 | ||
|
|
a83ff9253d | ||
|
|
8cdcdaf6d9 | ||
|
|
d208edf153 | ||
|
|
8ada4765b1 | ||
|
|
9d3a808c24 | ||
|
|
828eb80266 | ||
|
|
0639f6c1d1 | ||
|
|
49b971280f | ||
|
|
3682a1010d | ||
|
|
f8b58866dc | ||
|
|
5d8d815d84 | ||
|
|
67743c7597 | ||
|
|
b33cc90447 | ||
|
|
78177f7890 | ||
|
|
3c417bfa99 | ||
|
|
f2c8d80ff8 | ||
|
|
8c1eb1e19f | ||
|
|
eb7188e81b | ||
|
|
f352fc5c67 | ||
|
|
ac313722f9 | ||
|
|
64c7f1962c | ||
|
|
c83f994b21 | ||
|
|
10f38a72f7 | ||
|
|
da35434f58 | ||
|
|
c79409e094 | ||
|
|
355ce00968 | ||
|
|
56c832f3ba | ||
|
|
40abb2720e | ||
|
|
9b6051afee | ||
|
|
dcf6f26792 | ||
|
|
85734c1146 | ||
|
|
c3fb8fcb6e | ||
|
|
013bbc1638 | ||
|
|
b0669a3b60 | ||
|
|
6f5da1ce7c | ||
|
|
3351732a2f | ||
|
|
d4d4034ecd | ||
|
|
b45e2742c3 | ||
|
|
42eba290e4 | ||
|
|
32d1e08b57 | ||
|
|
7b7d19da7c | ||
|
|
f78e400918 | ||
|
|
be4e9bf7e9 | ||
|
|
48f2079f88 | ||
|
|
36ad1ceef2 | ||
|
|
1f2e2148d5 | ||
|
|
76f3d345f9 | ||
|
|
f2c854b53a | ||
|
|
e2d336d90b | ||
|
|
c74065ff26 | ||
|
|
b6d97dc5d5 | ||
|
|
1c1c26948a | ||
|
|
d954e3ffb7 | ||
|
|
d9759a95eb | ||
|
|
d196402dd0 | ||
|
|
0f2b5531de | ||
|
|
26e4398905 | ||
|
|
06995f22fe | ||
|
|
caa2073d48 | ||
|
|
d9466fe4bc | ||
|
|
72e623a3a7 | ||
|
|
69bc06685f | ||
|
|
bf175984f3 | ||
|
|
4d4a7bfd0d | ||
|
|
044cee30a5 | ||
|
|
3c0c5aed60 | ||
|
|
e194971727 | ||
|
|
bbe85def23 | ||
|
|
9c2d53f2ae | ||
|
|
1cf36e2156 | ||
|
|
ce96e309af | ||
|
|
9ea7dc8b3c | ||
|
|
282242efd2 | ||
|
|
351ba22d64 | ||
|
|
2916625747 | ||
|
|
744dbeb7a3 | ||
|
|
fbb3c309ee | ||
|
|
473c265bac | ||
|
|
01b06c2c3c | ||
|
|
8b1f95c743 | ||
|
|
ff5dbec579 | ||
|
|
9620c5a98f | ||
|
|
7e849a2e95 | ||
|
|
229aed306e | ||
|
|
d718969cbd | ||
|
|
8593f4c3a9 | ||
|
|
d4f71d5026 | ||
|
|
a683a5f206 | ||
|
|
7917cc3eb5 | ||
|
|
6cd7ca755e | ||
|
|
fb7dfdc800 | ||
|
|
e096af320f | ||
|
|
65bff8f511 | ||
|
|
ca19d8a842 | ||
|
|
cde08c64ce | ||
|
|
3d6d7b7cb9 | ||
|
|
a56ef6f398 | ||
|
|
5a38b6614d | ||
|
|
3d5c44e8aa | ||
|
|
46a68bd5e7 | ||
|
|
c77ded51a9 | ||
|
|
59bd9fdc22 | ||
|
|
b81941394c | ||
|
|
197d5ca1f2 | ||
|
|
97eb986fa6 | ||
|
|
aba3110e31 | ||
|
|
51c27be236 | ||
|
|
592255284f | ||
|
|
82fd41dd4c | ||
|
|
4ae8df9652 | ||
|
|
759b19e444 | ||
|
|
36f705e46f | ||
|
|
1f0347682e | ||
|
|
1aee385679 | ||
|
|
5dd8191692 | ||
|
|
b6422f9b80 | ||
|
|
a6c2c9c92f | ||
|
|
247c573a6e | ||
|
|
aa435d6e94 | ||
|
|
f6858c221b | ||
|
|
437aa4510b | ||
|
|
430572fb32 | ||
|
|
9f0b755d6f | ||
|
|
66acb55a57 | ||
|
|
f936d4c36e | ||
|
|
c3ddb8df56 | ||
|
|
81ce920e69 | ||
|
|
3cb875d139 | ||
|
|
fdb217d5c6 | ||
|
|
085cae3b5f | ||
|
|
e2f99a1554 | ||
|
|
aee0c27da7 | ||
|
|
accbf4ad8b | ||
|
|
0ac1901f6b | ||
|
|
c0a0deea78 | ||
|
|
e3f099441c | ||
|
|
840dd25cd1 | ||
|
|
a493969f9b | ||
|
|
cca42a10a1 | ||
|
|
2f4aa98c3c | ||
|
|
175cb9588c | ||
|
|
a8976e7559 | ||
|
|
b663528fb0 | ||
|
|
1a2ad55677 | ||
|
|
513d2a88c0 | ||
|
|
57ef4c0396 | ||
|
|
e9599ca2f4 | ||
|
|
2fc6dbc222 | ||
|
|
225933c442 | ||
|
|
36e2f048d7 | ||
|
|
0e158bce59 | ||
|
|
202201fd31 | ||
|
|
4f8c928f44 | ||
|
|
ada062cf00 | ||
|
|
32dc12912a | ||
|
|
671d21adf4 | ||
|
|
4e3f8e4b67 | ||
|
|
586bb62073 | ||
|
|
151bf83ab1 | ||
|
|
8c618edb5a | ||
|
|
6d6b0cf8c9 | ||
|
|
c610608890 | ||
|
|
28da61daab | ||
|
|
cbfc90f60b | ||
|
|
fec92959d4 | ||
|
|
bb00d81eba | ||
|
|
bb55644c06 | ||
|
|
16215847cd | ||
|
|
8ee9aed817 | ||
|
|
515c6402b9 | ||
|
|
d1b94ec203 | ||
|
|
a037d9167e | ||
|
|
3054d33e62 | ||
|
|
6651e641e1 | ||
|
|
35f873a342 | ||
|
|
2d03ff38a1 | ||
|
|
44a3db417b | ||
|
|
f9ca702a12 | ||
|
|
4073ff38eb | ||
|
|
f0e2c9f1b6 | ||
|
|
cf040f51b5 | ||
|
|
dcf919fb36 | ||
|
|
aa19b81a68 | ||
|
|
db8d77365c | ||
|
|
280eaea84a | ||
|
|
340ef46469 | ||
|
|
a5f8651941 | ||
|
|
8a18ee548e | ||
|
|
ef791aa22a | ||
|
|
4bdf2e1cc0 | ||
|
|
ffa0ca9379 | ||
|
|
d3b3d46fc1 | ||
|
|
7e7dd8efa9 | ||
|
|
b4506f1133 | ||
|
|
7350c79113 | ||
|
|
5f7b60d3f4 | ||
|
|
266495a611 | ||
|
|
f5b4a7e77b | ||
|
|
51ed0f6487 | ||
|
|
bd70def18a | ||
|
|
93a9cf3b31 | ||
|
|
751287999f | ||
|
|
d433850cbf | ||
|
|
c93e70e2dc | ||
|
|
a129dd989a | ||
|
|
40999c3ff4 | ||
|
|
18a01d672c | ||
|
|
3648f99920 | ||
|
|
e823412f56 | ||
|
|
d090f446c9 | ||
|
|
19985d1742 | ||
|
|
ab52251116 | ||
|
|
acb6c0187c | ||
|
|
90f9c24720 | ||
|
|
415bec4646 | ||
|
|
5559f20511 | ||
|
|
6ea3399829 | ||
|
|
9e3df97737 | ||
|
|
0ac40a4d77 | ||
|
|
24bf5e8102 | ||
|
|
56eebb03c7 | ||
|
|
1cd4d6d0df | ||
|
|
22d4a8232a | ||
|
|
48076c25bf | ||
|
|
478a308e73 | ||
|
|
8ca2f0a49f | ||
|
|
4a35d7364b | ||
|
|
8b2e55a969 | ||
|
|
9b14b70687 | ||
|
|
1c01087eda | ||
|
|
5137e5a301 | ||
|
|
0fea8365b8 | ||
|
|
116a615b84 | ||
|
|
ef272dd6a8 | ||
|
|
4462ae9cae | ||
|
|
66ffad0b4e | ||
|
|
81e0fc940c | ||
|
|
3e8794db1b | ||
|
|
617622d4d8 | ||
|
|
a4240f972b | ||
|
|
42feff8882 | ||
|
|
70edd9686f | ||
|
|
d298cb2e1c | ||
|
|
3fef3f2dc3 | ||
|
|
c85f9d6ae4 | ||
|
|
85e7ac7cd7 | ||
|
|
5c7db61a7e | ||
|
|
497f9ca0b1 | ||
|
|
0c50f7a322 | ||
|
|
740bcbd12c | ||
|
|
c92be4f270 | ||
|
|
e2a7271ab2 | ||
|
|
64766713fa | ||
|
|
59bba83b1d | ||
|
|
8ac1217165 | ||
|
|
5443629ec5 | ||
|
|
8f9b41cb62 | ||
|
|
7df5eba775 | ||
|
|
e29c6ac593 | ||
|
|
4f3190bf73 | ||
|
|
f0878fccb5 | ||
|
|
b0e121a53f | ||
|
|
eda48ab0e6 | ||
|
|
6a1014d8c1 | ||
|
|
e0f87315bc | ||
|
|
f2c5dca5e7 | ||
|
|
3c435a804f | ||
|
|
7ee6775251 | ||
|
|
98adc2ecc1 | ||
|
|
45afbbdac6 | ||
|
|
a18260747b | ||
|
|
0c80e28754 | ||
|
|
719bc374ac | ||
|
|
d7a290c595 | ||
|
|
4b2bd33baa | ||
|
|
efbe4d697c | ||
|
|
a8662bdb8b | ||
|
|
d822225465 | ||
|
|
c6373ef582 | ||
|
|
5a2af6d0f9 | ||
|
|
52000d6a41 | ||
|
|
befa13eaf2 | ||
|
|
4ac68a81a3 | ||
|
|
71e472f330 | ||
|
|
ada8809ec0 | ||
|
|
25ea518266 | ||
|
|
f3720c3b94 | ||
|
|
b942a84b15 | ||
|
|
574ca90229 | ||
|
|
5610a14e49 | ||
|
|
c5bc62e6df | ||
|
|
e9f6a85cad | ||
|
|
8921011b27 | ||
|
|
90ef3f6c94 | ||
|
|
e84c75f4a8 | ||
|
|
68e20cd860 | ||
|
|
76bdcea4b1 | ||
|
|
f758cb7e6e | ||
|
|
1108477810 | ||
|
|
deb6a9e51c | ||
|
|
9660f20b87 | ||
|
|
4d26468ede | ||
|
|
125a0979d5 | ||
|
|
98bdfd3dbe | ||
|
|
ea72880e74 | ||
|
|
17fec7d6e1 | ||
|
|
d9de64604e | ||
|
|
6275ab23ff | ||
|
|
8c0271643d | ||
|
|
c3f041dc87 | ||
|
|
80e5e84341 | ||
|
|
938eee80a9 | ||
|
|
7abc2289de | ||
|
|
bb79b9ed74 | ||
|
|
1e89a8625c | ||
|
|
90b0f04b3c | ||
|
|
0f019cd9b6 | ||
|
|
1209739398 | ||
|
|
b99db2b353 | ||
|
|
f057688e7d | ||
|
|
12ae2d0c76 | ||
|
|
9d91d5a127 | ||
|
|
6015f0887a | ||
|
|
d4c473d7b3 | ||
|
|
2f3978deed | ||
|
|
34af53a15b | ||
|
|
a4eb3d7a0b | ||
|
|
5b46dd7293 | ||
|
|
4b17847ea5 | ||
|
|
acbe000f97 | ||
|
|
599071b68b | ||
|
|
2bacee919d | ||
|
|
50d35e4196 | ||
|
|
e321998b85 | ||
|
|
340c02b2af | ||
|
|
69a295fe57 | ||
|
|
64830aae9f | ||
|
|
8969b1273f | ||
|
|
475faf7943 | ||
|
|
45b1d405a6 | ||
|
|
f60c8078e4 | ||
|
|
6701e49f9a | ||
|
|
0efb3c3284 | ||
|
|
aaf3cdfdac | ||
|
|
e00c261777 | ||
|
|
1eafed755d | ||
|
|
7f6a08ae50 | ||
|
|
9901816fb9 | ||
|
|
3cbe6c1e95 | ||
|
|
679c99274e | ||
|
|
b6fa0f1ff6 | ||
|
|
9ff64b95e1 | ||
|
|
3a9885abd8 | ||
|
|
503210942c | ||
|
|
0178e015e3 | ||
|
|
7604992c35 | ||
|
|
ee9e551788 | ||
|
|
22063248ca | ||
|
|
e4ce18b35c | ||
|
|
532890674e | ||
|
|
791a3b67e6 | ||
|
|
84b560ef29 | ||
|
|
03f081f3f4 | ||
|
|
08213ae86e | ||
|
|
73abc511a8 | ||
|
|
1c943cc259 | ||
|
|
af62a89a6b | ||
|
|
af7ca5b897 | ||
|
|
27356ef747 | ||
|
|
b27f80ef87 | ||
|
|
b4aa73fc64 | ||
|
|
bbf444572b | ||
|
|
b3706574de | ||
|
|
83062f8bfb | ||
|
|
1d7fcfdaa1 | ||
|
|
58ab12b4cb | ||
|
|
28c649629f | ||
|
|
5ec190225d | ||
|
|
0e0d404997 | ||
|
|
3f16b908ca | ||
|
|
54549b261c | ||
|
|
8ce07ced9e | ||
|
|
37a5144e8f | ||
|
|
59fed7dda8 | ||
|
|
0a85a2868c | ||
|
|
0d493b3250 | ||
|
|
74530d4b1e | ||
|
|
7548c52e21 | ||
|
|
fa49844c64 | ||
|
|
3508ae1e0a | ||
|
|
f013ee64a2 | ||
|
|
af3da7ca6e | ||
|
|
cb728f65b3 | ||
|
|
af6af190cc | ||
|
|
cbcc8455ca | ||
|
|
af35fb79fe | ||
|
|
0515aaa946 | ||
|
|
1d00330e7a | ||
|
|
3fa6dcea16 | ||
|
|
51425cac4a | ||
|
|
7ce61a5d2b | ||
|
|
5e5caa979b | ||
|
|
a719518f8f | ||
|
|
d0e5aef443 | ||
|
|
b82a811c33 | ||
|
|
3356dcf8f7 | ||
|
|
8f1cc26537 | ||
|
|
27dafa83ac | ||
|
|
c1be1f329f | ||
|
|
c757cef549 | ||
|
|
da9e4c026c | ||
|
|
5821d67e69 | ||
|
|
ed14f6d13f | ||
|
|
35bdd8b4ef | ||
|
|
9cf2d30e77 | ||
|
|
a2140a3b7b | ||
|
|
900d026bcb | ||
|
|
68604ec15a | ||
|
|
f7c0ebb8d3 | ||
|
|
8be5d0c72d | ||
|
|
15c8b724e6 | ||
|
|
d6949200f9 | ||
|
|
1c2abe794a | ||
|
|
0bcf393586 | ||
|
|
bc0573918f | ||
|
|
175675ad99 | ||
|
|
b00c2afc46 | ||
|
|
c125a7fe07 | ||
|
|
a42ab32436 | ||
|
|
797a64976e | ||
|
|
ac377a8b68 | ||
|
|
842b7b1402 | ||
|
|
d449396ad5 | ||
|
|
532a87d064 | ||
|
|
e1cdc7b387 | ||
|
|
d9d917e267 | ||
|
|
8048788042 | ||
|
|
790fc88e47 | ||
|
|
7f970d489f | ||
|
|
9d85ca07f4 | ||
|
|
d75e3acdf3 | ||
|
|
694cda0e99 | ||
|
|
94f134c6a7 | ||
|
|
83f329d93c | ||
|
|
de49387fca | ||
|
|
e5567f2f46 | ||
|
|
80f545f3d5 | ||
|
|
06f0cc70b8 | ||
|
|
cf6b7544b0 | ||
|
|
d511c6334a | ||
|
|
0224fd6d54 | ||
|
|
e95ae8dbcf | ||
|
|
5fbd64da71 | ||
|
|
b282a2a621 | ||
|
|
9a7a534051 | ||
|
|
52fd030b6e | ||
|
|
dfe530a764 | ||
|
|
b079956075 | ||
|
|
e30037c4d1 | ||
|
|
eda7be627c | ||
|
|
af0a649656 | ||
|
|
8b6a3c4236 | ||
|
|
0988a92d8a | ||
|
|
9cf67764b7 | ||
|
|
6c4e1d1c41 | ||
|
|
b1cd1ea8b3 | ||
|
|
5169ed494c | ||
|
|
824211c31b | ||
|
|
be3dd83bc7 | ||
|
|
c2911c1e48 | ||
|
|
9a66c5c07d | ||
|
|
34d393b986 | ||
|
|
d4da02318d | ||
|
|
47162af6d5 | ||
|
|
8cd6a72dd3 | ||
|
|
ba0a183b6f | ||
|
|
73781c7edb | ||
|
|
6d99852c81 | ||
|
|
9325c726fd | ||
|
|
947bb8530e | ||
|
|
2b32f316ee | ||
|
|
4b1f23a189 | ||
|
|
71d220e7a4 | ||
|
|
747d0d0d17 | ||
|
|
5c72e6d335 | ||
|
|
e1ac2b0e10 | ||
|
|
0ba94f3bc9 | ||
|
|
ba0bfafcd5 | ||
|
|
81adb80b7e | ||
|
|
cb238ef170 | ||
|
|
b9b921de82 | ||
|
|
d10e31c278 | ||
|
|
fd641d77c7 | ||
|
|
2aa9710dd1 | ||
|
|
4afb2ef2b8 | ||
|
|
a54e6e7c4b | ||
|
|
7af26a356f | ||
|
|
2f66165f8c | ||
|
|
e86ce8fc38 | ||
|
|
9b48c65129 | ||
|
|
434cd133df | ||
|
|
aa91e4cdee | ||
|
|
d57c1e7ff0 | ||
|
|
13e97703e5 | ||
|
|
c597b293b8 | ||
|
|
6620d64ce7 | ||
|
|
2ae120c878 | ||
|
|
7a25035fb1 | ||
|
|
bf4052b550 | ||
|
|
5ca5ad2cee | ||
|
|
2848f1e13c | ||
|
|
f7895823cb | ||
|
|
b841c5c5e5 | ||
|
|
a7952a4633 | ||
|
|
d047d26dbf | ||
|
|
ddedc1640f | ||
|
|
021d7e5efc | ||
|
|
332e528012 | ||
|
|
a0155da06b | ||
|
|
d58d22adbe | ||
|
|
653352bff0 | ||
|
|
4f5b33d8df | ||
|
|
7e7d83ac36 | ||
|
|
0de5c043bb | ||
|
|
ec2769ea3c | ||
|
|
3f742f5f8e | ||
|
|
50849101d4 | ||
|
|
1ccf4a74c9 | ||
|
|
7cd4967963 | ||
|
|
21e2700da5 | ||
|
|
395a68ad80 | ||
|
|
b7f0132675 | ||
|
|
4ac827b9e8 | ||
|
|
d96963862e | ||
|
|
53217b061d | ||
|
|
4770daa7c6 | ||
|
|
2b709ad094 | ||
|
|
378b81b13b | ||
|
|
56ee72214f | ||
|
|
2e7c3167f5 | ||
|
|
a2fb728d3b | ||
|
|
be1c936e90 | ||
|
|
af8037ab03 | ||
|
|
3b8dc98226 | ||
|
|
b411b4e61b | ||
|
|
436eb30490 | ||
|
|
09b8087787 | ||
|
|
4ac4c69820 | ||
|
|
3ebc5c0865 | ||
|
|
7b94f8f105 | ||
|
|
ec994b3e97 | ||
|
|
86ae0182fd | ||
|
|
bfa891f0ca | ||
|
|
ef843cac63 | ||
|
|
78d68d0a4f | ||
|
|
4bceba777d | ||
|
|
2e537b1e5e | ||
|
|
df8463b625 | ||
|
|
f40371e0cf | ||
|
|
a565e7aed6 | ||
|
|
c948361090 | ||
|
|
4cf2b74a01 | ||
|
|
de9c8362ac | ||
|
|
b5bb5d35e7 | ||
|
|
d910cfa919 | ||
|
|
5857e3d5c6 | ||
|
|
25daa343c6 | ||
|
|
85224c8f0c | ||
|
|
5f8583e3eb | ||
|
|
062821d267 | ||
|
|
1fdaaf82d2 | ||
|
|
e4c1b17810 | ||
|
|
70057542c1 | ||
|
|
f2255ee707 | ||
|
|
791cc70b09 | ||
|
|
24dcb9973b | ||
|
|
adfd0d5c18 | ||
|
|
cfb128acb8 | ||
|
|
1e8e246ffb | ||
|
|
6162244730 | ||
|
|
86cbdf2442 | ||
|
|
5334626efb | ||
|
|
708d473b47 | ||
|
|
ead954ddaa | ||
|
|
294f511b9a | ||
|
|
b5ebcc3e07 | ||
|
|
7b8e7ac5c2 | ||
|
|
904d39beac | ||
|
|
8958b61fdd | ||
|
|
09293f7d9a | ||
|
|
d520c3d674 | ||
|
|
430e616328 | ||
|
|
976ad5fcee | ||
|
|
4d58ee2162 | ||
|
|
03b6abe5ec | ||
|
|
4479304f3f | ||
|
|
ed83405254 | ||
|
|
b815d945d9 | ||
|
|
2b8e024f48 | ||
|
|
2a0399b98d | ||
|
|
4e333e2d75 | ||
|
|
4f25b7bbbe | ||
|
|
3678db649b | ||
|
|
9d8b1fd99b | ||
|
|
7b62c06be3 | ||
|
|
bc978a91e3 | ||
|
|
611ba97b60 | ||
|
|
39dff1ea7c | ||
|
|
bcf7bcba1d | ||
|
|
8f00730189 | ||
|
|
1dde79ace2 | ||
|
|
5954fb91be | ||
|
|
e192383662 | ||
|
|
4a20fad4e5 | ||
|
|
a469e0864e | ||
|
|
cfce2bdbd9 | ||
|
|
529b5c0a00 | ||
|
|
7cc5787779 | ||
|
|
48eeb279e2 | ||
|
|
d67566252a | ||
|
|
8a2d79e17d | ||
|
|
17370dff54 | ||
|
|
f08a8e7634 | ||
|
|
83543bbddc | ||
|
|
b898c90f41 | ||
|
|
1ec927cf4f | ||
|
|
aef21ba9d5 | ||
|
|
0b31709aee | ||
|
|
7c3433256a | ||
|
|
c79c638f35 | ||
|
|
a148941a39 | ||
|
|
23abe26b0b | ||
|
|
8f21d6dde0 | ||
|
|
b74a6c9e03 | ||
|
|
b63f73ef93 | ||
|
|
1f0b369a15 | ||
|
|
7a43473513 | ||
|
|
07f367a2a5 | ||
|
|
c52ad68d92 | ||
|
|
85467dbd2a | ||
|
|
aa767846f0 | ||
|
|
6d3ad15d20 | ||
|
|
7b95db4d13 | ||
|
|
160b3ff655 | ||
|
|
757a39a75e | ||
|
|
4c08a527be | ||
|
|
578b7b0512 | ||
|
|
45e0259099 | ||
|
|
1ac1933ec1 | ||
|
|
43353e89bb | ||
|
|
010e4610f7 | ||
|
|
ba38853406 | ||
|
|
4944a9e51e | ||
|
|
73ad97859c | ||
|
|
e600da229c | ||
|
|
273b4f20db | ||
|
|
9843dccdf0 | ||
|
|
60ae1ec1e8 | ||
|
|
650c6670f2 | ||
|
|
d6dcba7a60 | ||
|
|
5b3849082f | ||
|
|
5de988a2e6 | ||
|
|
26220b2f54 | ||
|
|
8d8436f1f1 | ||
|
|
93057d9449 | ||
|
|
2addcc3ab5 | ||
|
|
b2419abdc8 | ||
|
|
9f654918ae | ||
|
|
cb086fe08d | ||
|
|
1482166ba3 | ||
|
|
6b8dd9bf03 | ||
|
|
a8af9da249 | ||
|
|
e410f82fdb | ||
|
|
4f70f228f1 | ||
|
|
ddc97b99fa | ||
|
|
5f42918cc9 | ||
|
|
e6dfe6fe89 | ||
|
|
fdbe693139 | ||
|
|
0633b2876c | ||
|
|
ee45ed8ec2 | ||
|
|
0c6726e3f7 | ||
|
|
2a75995189 | ||
|
|
9f074c1ab7 | ||
|
|
2917ee3b9d | ||
|
|
03f631f537 | ||
|
|
fb4171ae6f | ||
|
|
aec7d5aff7 | ||
|
|
045c225c8c | ||
|
|
c49ef49371 | ||
|
|
9fd012229b | ||
|
|
bb27099f9e | ||
|
|
411634ecda | ||
|
|
bb59f66008 | ||
|
|
b8a0f34c33 | ||
|
|
784955cdbd | ||
|
|
83764ce3d5 | ||
|
|
438cf271c0 | ||
|
|
8631eacdf3 | ||
|
|
6e7ce10585 | ||
|
|
3d4a58cb96 | ||
|
|
6fde3f8453 | ||
|
|
016d2f7ab1 | ||
|
|
a8ffc414fe | ||
|
|
b0c1c6e880 | ||
|
|
d3e6177a2b | ||
|
|
8adbdcc675 | ||
|
|
d1d7ff0d0a | ||
|
|
4b05a49b46 | ||
|
|
0309a866c8 | ||
|
|
bbc77f81ca | ||
|
|
483f6f2188 | ||
|
|
e0ef3ca39a | ||
|
|
aef29ff6c5 | ||
|
|
ec7bd8c1c8 | ||
|
|
fe0d69739d | ||
|
|
448e3a4639 | ||
|
|
2e74d6f652 | ||
|
|
237108a6d1 | ||
|
|
ed3a222ed4 | ||
|
|
8a696f6c52 | ||
|
|
184aa4fd20 | ||
|
|
ce14b9e43e | ||
|
|
1cdedb89f1 | ||
|
|
18efb098f9 | ||
|
|
fb3a27c02f | ||
|
|
6d41bcd511 | ||
|
|
e6b981fc38 | ||
|
|
74a3ea2a01 | ||
|
|
4bfb7a5d29 | ||
|
|
55ba040e3d | ||
|
|
e4e78e2083 | ||
|
|
82f25cc2e5 | ||
|
|
7b67c9aa8d | ||
|
|
854f3b5257 | ||
|
|
e37bc99c36 | ||
|
|
52121b90ff | ||
|
|
3eb9de57ef | ||
|
|
4ed501f4d4 | ||
|
|
0bb8ae540d | ||
|
|
963957f7a6 | ||
|
|
93a152068d | ||
|
|
bb318aefd2 | ||
|
|
0cafef7ab4 | ||
|
|
055e54966d | ||
|
|
c58254f951 | ||
|
|
ce710134ac | ||
|
|
fa68bdc82b | ||
|
|
cd9f2ab232 | ||
|
|
91136fe48f | ||
|
|
40cc0f721d | ||
|
|
72567c43e6 | ||
|
|
db82d7a6b3 | ||
|
|
c469a61908 | ||
|
|
1c4bcc5697 | ||
|
|
0f3b0309e8 | ||
|
|
5a393de1cb | ||
|
|
1617a61b33 | ||
|
|
ccd18c4f10 | ||
|
|
e287f5774a | ||
|
|
2dec3195ac | ||
|
|
2bea6694c7 | ||
|
|
38c468a204 | ||
|
|
6a80258163 | ||
|
|
55fe2600b7 | ||
|
|
cad4e183c5 | ||
|
|
ba447d2b00 | ||
|
|
fa5e32c46b | ||
|
|
e51b7c22a6 | ||
|
|
0473cdc5e1 | ||
|
|
36c65e8b5f | ||
|
|
d0e81b7778 | ||
|
|
7df63c87c9 | ||
|
|
0021f7b4ba | ||
|
|
540283970a | ||
|
|
5a2b9b60e6 | ||
|
|
12e09269d3 | ||
|
|
bcad5eda81 | ||
|
|
09ad56056e | ||
|
|
0c8dc63085 | ||
|
|
68e3bcba05 | ||
|
|
75a6111a9b | ||
|
|
1d9f970169 | ||
|
|
7cc328ed3b | ||
|
|
9a1c88c80d | ||
|
|
b1446438fb | ||
|
|
6ba1d71a81 | ||
|
|
a4f885a2c5 | ||
|
|
a8a3c63f64 | ||
|
|
ace7b4e2af | ||
|
|
a2885a90b7 | ||
|
|
4457487e2a | ||
|
|
830bb5f70d | ||
|
|
9106f9676c | ||
|
|
df1690515c | ||
|
|
8e287da7b5 | ||
|
|
128ebe2cb2 | ||
|
|
404964dfde | ||
|
|
7f74e79253 | ||
|
|
11b5cc83dc | ||
|
|
bda9d1f8e2 | ||
|
|
32efeee4c7 | ||
|
|
8adbd90cc3 | ||
|
|
074fcaaa73 | ||
|
|
d2209c8ead | ||
|
|
31f1079dfa | ||
|
|
2b87c83fa7 | ||
|
|
1c31c1947c | ||
|
|
50a8a83408 | ||
|
|
020fa4eefa | ||
|
|
64cf02bc45 | ||
|
|
06bd964adc | ||
|
|
2218b7b853 | ||
|
|
f6c45cd85a | ||
|
|
cf4907011b | ||
|
|
dea85add5d | ||
|
|
aa3325ef35 | ||
|
|
8059ba7306 | ||
|
|
c346c4178d | ||
|
|
f2555563a8 | ||
|
|
b948961d52 | ||
|
|
43987d844f | ||
|
|
cc3b14dbf8 | ||
|
|
3cc2d3260e | ||
|
|
be155f8c6c | ||
|
|
74952cf62d | ||
|
|
4a71df7859 | ||
|
|
7d7ea4c34b | ||
|
|
026d22f7f9 | ||
|
|
babd351151 | ||
|
|
d11894fd74 | ||
|
|
6072eab01d | ||
|
|
64e4c77428 | ||
|
|
48b70d0254 | ||
|
|
db2015f826 | ||
|
|
f893cf268b | ||
|
|
ad12d42b35 | ||
|
|
63a0c7c10b | ||
|
|
99d4d5ed9a | ||
|
|
7910c28be7 | ||
|
|
32e1ade388 | ||
|
|
a8aac7e1b4 | ||
|
|
7404aaf4d2 | ||
|
|
2f25bd476c | ||
|
|
681e4ae7fc | ||
|
|
e1ed0d2b09 | ||
|
|
32567a1c66 | ||
|
|
25753af331 | ||
|
|
6babcf9536 | ||
|
|
9a05f659f1 | ||
|
|
ca58bf661d | ||
|
|
62695d4d9a | ||
|
|
d723ef0a13 | ||
|
|
18160818d5 | ||
|
|
83bfaabd95 | ||
|
|
e23ac8496e | ||
|
|
2b7d05df6b | ||
|
|
606cb249e8 | ||
|
|
0ae739b437 | ||
|
|
f18e57b50e | ||
|
|
4c92c582d7 | ||
|
|
7b1bfeaca1 | ||
|
|
dd1d0d1cb8 | ||
|
|
995bd4953b | ||
|
|
32b61ac730 | ||
|
|
19bd54f425 | ||
|
|
76680777ff | ||
|
|
75b9a8a826 | ||
|
|
f439541844 | ||
|
|
65fb21a062 | ||
|
|
aa5df3c309 | ||
|
|
788394a1c8 | ||
|
|
7286c2f603 | ||
|
|
aff230ec7e | ||
|
|
3d538d0566 | ||
|
|
a1b287bd9d | ||
|
|
1d187f525d | ||
|
|
6c9ab0c019 | ||
|
|
e7157a8528 | ||
|
|
e2182344a2 | ||
|
|
9ebd882c3f | ||
|
|
f017b6b92f | ||
|
|
1c24c4e776 | ||
|
|
758e94a8ec | ||
|
|
9a7199bf7f | ||
|
|
1c691fa2c2 | ||
|
|
b435f49611 | ||
|
|
bcd7b2552e | ||
|
|
e76126fdd9 | ||
|
|
3fe55e7395 | ||
|
|
98e11c16fa | ||
|
|
0c5b2d302b | ||
|
|
0d526a2c4f | ||
|
|
0a8d49c233 | ||
|
|
4618d3e38a | ||
|
|
5521785b87 | ||
|
|
f12b404d58 | ||
|
|
6924e3a7d7 | ||
|
|
6fbe459903 | ||
|
|
0a245e5d65 | ||
|
|
2ed593c5e0 | ||
|
|
ce934b8998 | ||
|
|
aac4d3eefb | ||
|
|
176dc17c70 | ||
|
|
553bbda769 | ||
|
|
d4fd2d9ae0 | ||
|
|
c20138c9fc | ||
|
|
7f1ff11a4c | ||
|
|
ed9121a06f | ||
|
|
c110611d82 | ||
|
|
cc222a6f4a | ||
|
|
8293709f34 | ||
|
|
707740b7f1 | ||
|
|
98de661868 | ||
|
|
64df057e2f | ||
|
|
9d9c5cff75 | ||
|
|
3ed6f360da | ||
|
|
9f07f6441a | ||
|
|
fc0b326443 | ||
|
|
4ac0483ec6 | ||
|
|
724e78d4d5 | ||
|
|
6efa0490ea | ||
|
|
99e032eeb7 | ||
|
|
c80c212a54 | ||
|
|
95460038cf | ||
|
|
586e3b891c | ||
|
|
4402645b37 | ||
|
|
42624246bd | ||
|
|
ddd39ea0c9 | ||
|
|
a95cde372c | ||
|
|
dcbf03c355 | ||
|
|
ce44fb29c9 | ||
|
|
35f5df63e2 | ||
|
|
60c9200c06 | ||
|
|
25dacccb3b | ||
|
|
c0d08d966a | ||
|
|
cdfae2e8cb | ||
|
|
a3b0c351d0 | ||
|
|
92719d095a | ||
|
|
b2dc397fb0 | ||
|
|
55e09529c1 | ||
|
|
aed91d42bf | ||
|
|
eef58f4da0 | ||
|
|
4c427116a8 | ||
|
|
00e9624670 | ||
|
|
b52878fc97 | ||
|
|
68d9984d64 | ||
|
|
c4d293143d | ||
|
|
b08d91a218 | ||
|
|
1c586d8811 | ||
|
|
f5e13c25a7 | ||
|
|
6e267b8825 | ||
|
|
6cdfe70ac9 | ||
|
|
e8a0c693c9 | ||
|
|
ac7db87592 | ||
|
|
3d37a9eb8b | ||
|
|
8c5613b182 | ||
|
|
9e068335b6 | ||
|
|
19ed98f265 | ||
|
|
2aff36a1c0 | ||
|
|
eac7fccbb4 | ||
|
|
7562eaeb5f | ||
|
|
7b38683985 | ||
|
|
27763afbbe | ||
|
|
7a2b6f63c9 | ||
|
|
2ad8413c6d | ||
|
|
956c9000b8 | ||
|
|
838c2994a9 | ||
|
|
9974095659 | ||
|
|
85e289fd4e | ||
|
|
fa17e7019a | ||
|
|
edcb24068a | ||
|
|
38ead4909e | ||
|
|
06b2eb7662 | ||
|
|
5685f2699b | ||
|
|
c6916fd71e | ||
|
|
df64abaf9c | ||
|
|
6ed717556a | ||
|
|
e8c64074f3 | ||
|
|
4af13058f0 | ||
|
|
f3f223fa22 | ||
|
|
e244b4b263 | ||
|
|
ab09810bef | ||
|
|
fbcde465ad | ||
|
|
0f0d168976 | ||
|
|
33d15ca66d | ||
|
|
4fad97e8b1 | ||
|
|
81071771d9 | ||
|
|
4db3bafeda | ||
|
|
29aff298b8 | ||
|
|
4271289db0 | ||
|
|
9818e7c101 | ||
|
|
121a865bb8 | ||
|
|
7af340de85 | ||
|
|
cdc0dee6f5 | ||
|
|
4cbefff8b4 | ||
|
|
acc562bd7f | ||
|
|
44b87dca2a | ||
|
|
be98f786b3 | ||
|
|
6f215cbff9 | ||
|
|
96a29e91e8 | ||
|
|
e01a1a456c | ||
|
|
2561a6e00e | ||
|
|
e0bee0e0db | ||
|
|
7910d006ca | ||
|
|
778a651f7b | ||
|
|
2267dcb768 | ||
|
|
21e7c78744 | ||
|
|
54418957b6 | ||
|
|
77ce31128c | ||
|
|
79ce990644 | ||
|
|
2262d44638 | ||
|
|
972601caf6 | ||
|
|
114ebac21b | ||
|
|
57a86cd836 | ||
|
|
a741b1b9ac | ||
|
|
5d64ea48ba | ||
|
|
db6c65c7e8 | ||
|
|
b68fc6187f | ||
|
|
766a190015 | ||
|
|
2c17551d50 | ||
|
|
739bc361bd | ||
|
|
100fc20928 | ||
|
|
f42e79f5cf | ||
|
|
04da989108 | ||
|
|
11017dd8dc | ||
|
|
8d0aa65ab2 | ||
|
|
8ffb9e6f11 | ||
|
|
b0487798c2 | ||
|
|
740b89258d | ||
|
|
9026921214 | ||
|
|
df2aac3946 | ||
|
|
829e004ee5 | ||
|
|
072384eede | ||
|
|
dcc8043cf6 | ||
|
|
62c9484208 | ||
|
|
ebadccbe25 | ||
|
|
8013716158 | ||
|
|
ed7904b673 | ||
|
|
b2e3d33c80 | ||
|
|
7d1b4d916e | ||
|
|
0fb45d4584 | ||
|
|
0008919631 | ||
|
|
6bf8d19775 | ||
|
|
2e3ef87c42 | ||
|
|
abdd5cf19b | ||
|
|
8a636736b4 | ||
|
|
8fe2529306 | ||
|
|
6db217387f | ||
|
|
d33172df11 | ||
|
|
206a83c935 | ||
|
|
6a0935960b | ||
|
|
3665a792d8 | ||
|
|
90a58a1f37 | ||
|
|
f81eaf62ff | ||
|
|
8567f9b19f | ||
|
|
e33fc1a89b | ||
|
|
872ec90654 | ||
|
|
921f5524e7 | ||
|
|
a65959de3f | ||
|
|
bc095d087c | ||
|
|
3db744269e | ||
|
|
701e2f819b | ||
|
|
88bf569752 | ||
|
|
7283f48b36 | ||
|
|
f677b63c5c | ||
|
|
b2e4172c12 | ||
|
|
e0bec82a1c | ||
|
|
6cf117b3d0 | ||
|
|
6bcf47fc4b | ||
|
|
6d968bbbd8 | ||
|
|
8b803faf72 | ||
|
|
d9bcb16d9e | ||
|
|
ead9f0b3c6 | ||
|
|
6168621a36 | ||
|
|
d48c257b19 | ||
|
|
d723bbe3b7 | ||
|
|
f68e9c7681 | ||
|
|
f7a59edf19 | ||
|
|
a4741e28c5 | ||
|
|
c48ca99817 | ||
|
|
2b6965f801 | ||
|
|
caec4f80bb | ||
|
|
9aa7672b17 | ||
|
|
8e03cf9fc8 | ||
|
|
8d9a050ad6 | ||
|
|
266e08817e | ||
|
|
6c3526d47e | ||
|
|
19adfd5116 | ||
|
|
485f7fdcef | ||
|
|
856afcf3bf | ||
|
|
934b5e344f | ||
|
|
82eb8f14aa | ||
|
|
caa9cc213d | ||
|
|
403325d9dd | ||
|
|
62b3ca2fb9 | ||
|
|
e6034223f8 | ||
|
|
da02bea945 | ||
|
|
dd308d9276 | ||
|
|
fce72cad55 | ||
|
|
901b23e5ae | ||
|
|
df4d4d36bc | ||
|
|
408c56bfcf | ||
|
|
ea75ddfa85 | ||
|
|
bda5619b97 | ||
|
|
f6fb3cc766 | ||
|
|
7c3cb118a4 | ||
|
|
d03565ad95 | ||
|
|
c0f5746d1d | ||
|
|
a686bec025 | ||
|
|
c0d36bb85e | ||
|
|
e6df8dcd2e | ||
|
|
6f3fd99130 | ||
|
|
b5f7106780 | ||
|
|
e2f6dcab01 | ||
|
|
ff2f218bf0 | ||
|
|
902ed6e287 | ||
|
|
a40b49f2ed | ||
|
|
fc3b55b135 | ||
|
|
07bd5c633e | ||
|
|
f70dc14340 | ||
|
|
3d49ab2ae0 | ||
|
|
a133c39167 | ||
|
|
c9c8e20511 | ||
|
|
509ff7e833 | ||
|
|
a7a5fdb7f5 | ||
|
|
cf9ef281ce | ||
|
|
6345335be2 | ||
|
|
a008ec3729 | ||
|
|
e7a48f3909 | ||
|
|
718553373f | ||
|
|
310772d7fe | ||
|
|
213e6fab57 | ||
|
|
b7dbed5cd4 | ||
|
|
b621f2c24d | ||
|
|
21b9b090f9 | ||
|
|
709121b07f | ||
|
|
bb02a17e44 | ||
|
|
fd40c58b9c | ||
|
|
b7a6f1249d | ||
|
|
71125f07cc | ||
|
|
8500f5b437 | ||
|
|
eb25d9d7af | ||
|
|
033c85fc4b | ||
|
|
1663731489 | ||
|
|
1f65d47811 | ||
|
|
897cba3d07 | ||
|
|
f0e2247f06 | ||
|
|
1470ec60db | ||
|
|
7c04792777 | ||
|
|
6e750a2af0 | ||
|
|
7640dab380 | ||
|
|
6c10908cb7 | ||
|
|
abd235f332 | ||
|
|
4a638e82cc | ||
|
|
156d39490e | ||
|
|
a39968868f | ||
|
|
e0587ad17b | ||
|
|
06106f3831 | ||
|
|
1e332ed075 | ||
|
|
cef56cb5f1 | ||
|
|
97762712e6 | ||
|
|
4575996995 | ||
|
|
9a4594227e | ||
|
|
399bec62c2 | ||
|
|
177d015fc6 | ||
|
|
afd62ceeeb | ||
|
|
44c088c96d | ||
|
|
cf65347cd8 | ||
|
|
9fe54c5d21 | ||
|
|
0210ef2764 | ||
|
|
c66982c9bc | ||
|
|
a88be30c3f | ||
|
|
155cd31ced | ||
|
|
52dc77a00f | ||
|
|
c5de56790a | ||
|
|
d97cb4c360 | ||
|
|
8e499d164a | ||
|
|
764e1ac35f | ||
|
|
69e4fcfc76 | ||
|
|
951071e7ba | ||
|
|
5e82700871 | ||
|
|
4c706a213a | ||
|
|
d0da1f43a9 | ||
|
|
4afc439169 | ||
|
|
e47690dd52 | ||
|
|
7859aa0ce8 | ||
|
|
710c192e39 | ||
|
|
50e179f7a8 | ||
|
|
980a9d0640 | ||
|
|
fdfbf54808 | ||
|
|
fe8b0d99e5 | ||
|
|
7e817a5808 | ||
|
|
23b68bd9f9 | ||
|
|
0a8bbc7d3a | ||
|
|
0f5200aaf8 | ||
|
|
82d7b9cf05 | ||
|
|
0f5fafac8d | ||
|
|
7fcfffba6f | ||
|
|
13e9ef4c34 | ||
|
|
ea04c7d5eb | ||
|
|
8bf29528e3 | ||
|
|
c6f65debcf | ||
|
|
370b39a139 | ||
|
|
7b7e64576f | ||
|
|
d8b717a879 | ||
|
|
3967017ebb | ||
|
|
d93862043a | ||
|
|
719276b32a | ||
|
|
bf13f06e68 | ||
|
|
9f770d08f5 | ||
|
|
e62c529c1f | ||
|
|
4e1fc9b031 | ||
|
|
7d175ad38d | ||
|
|
de2c56560e | ||
|
|
0db413cb18 | ||
|
|
7c6424e05c | ||
|
|
3c328a00b5 | ||
|
|
e36f13c595 | ||
|
|
38a5cc33e8 | ||
|
|
245a3a73d8 | ||
|
|
d57d7ec4d8 | ||
|
|
09a4e4a6a5 | ||
|
|
d9beb4b660 | ||
|
|
63507a5567 | ||
|
|
e42dc259d8 | ||
|
|
f59f8d1937 | ||
|
|
0387bde81b | ||
|
|
b8fe95b945 | ||
|
|
792a1652e3 | ||
|
|
4816bfa26d | ||
|
|
95d1680ac3 | ||
|
|
5e1e6aea16 | ||
|
|
00696943d1 | ||
|
|
c1d293f9f9 | ||
|
|
d4d61d10bc | ||
|
|
c09e5e6552 | ||
|
|
ab08732ba8 | ||
|
|
584a7eb9e2 | ||
|
|
d44b04f425 | ||
|
|
eeec04b21a | ||
|
|
0642bc28bc | ||
|
|
2b12b67582 | ||
|
|
d9b575b051 | ||
|
|
c6dbb41724 | ||
|
|
ea4c1bf5e5 | ||
|
|
67afc050b8 | ||
|
|
6e90da90f1 | ||
|
|
26872c829b | ||
|
|
34f32c8ba5 | ||
|
|
b0d847dc18 | ||
|
|
adabef9706 | ||
|
|
474adf2d53 | ||
|
|
4f23ad37f5 | ||
|
|
eb6b160377 | ||
|
|
0770e00d8d | ||
|
|
ab0e95177e | ||
|
|
cfd9e4b256 | ||
|
|
bfed0ed791 | ||
|
|
3cec9f7ee0 | ||
|
|
3a5dc62908 | ||
|
|
e0cdfcf403 | ||
|
|
439866216c | ||
|
|
a1b0731665 | ||
|
|
bcc216fd2e | ||
|
|
574d848828 | ||
|
|
22acc2307c | ||
|
|
d01a3c327e | ||
|
|
7c52b6ab23 | ||
|
|
23d27aee6b | ||
|
|
9315358fa6 | ||
|
|
83aaa94cfe | ||
|
|
7744099ee5 | ||
|
|
35bb6b62d8 | ||
|
|
99844931f4 | ||
|
|
d8c34a7cdf | ||
|
|
c284edcd81 | ||
|
|
f5d4b352b3 | ||
|
|
e44b31d755 | ||
|
|
3a28068b48 | ||
|
|
03433b00d2 | ||
|
|
6f39ebf48f | ||
|
|
949ca00dbb | ||
|
|
bfdf1ee8f5 | ||
|
|
e047d4e8a6 | ||
|
|
15cf2069b6 | ||
|
|
564b0ee0c4 | ||
|
|
b454932ff5 | ||
|
|
191c07b6a1 | ||
|
|
5b0deb9fc2 | ||
|
|
297ec64bde | ||
|
|
a9b85caeb7 | ||
|
|
e30b883148 | ||
|
|
cd6be0d755 | ||
|
|
95b74c5f2f | ||
|
|
45f26bb22e | ||
|
|
09b0e09932 | ||
|
|
ea9d88009f | ||
|
|
e5013c918e | ||
|
|
4b45103a4e | ||
|
|
1ea28411da | ||
|
|
e27fce1227 | ||
|
|
cf3b8378bd | ||
|
|
9f07532140 | ||
|
|
f1aedc619e | ||
|
|
5f778e706f | ||
|
|
ee15663679 | ||
|
|
c271d4bfe4 | ||
|
|
99188f7427 | ||
|
|
88a07ecf63 | ||
|
|
f1af2e35cd | ||
|
|
352e20f01b | ||
|
|
f7d10a6cf1 | ||
|
|
6a5808077c | ||
|
|
09be11b5a8 | ||
|
|
5117c1a528 | ||
|
|
ead9857203 | ||
|
|
cd3493e040 | ||
|
|
02c22c1894 | ||
|
|
8b41a03080 | ||
|
|
306f4f119b | ||
|
|
3dc28c7291 | ||
|
|
f2f2215059 | ||
|
|
f75179b2f6 | ||
|
|
3ead35c984 | ||
|
|
efe20a1fe8 | ||
|
|
ab1e56a14e | ||
|
|
055d6bf601 | ||
|
|
683c3e96d6 | ||
|
|
d8e414a4a2 | ||
|
|
9237d68b79 | ||
|
|
12a14fbe29 | ||
|
|
22e4e4efc1 | ||
|
|
588327fd68 | ||
|
|
d35ef2d360 | ||
|
|
5ce5193430 | ||
|
|
6e74215b2e | ||
|
|
884a9835c4 | ||
|
|
233f40f3e9 | ||
|
|
9b24e61033 | ||
|
|
286928d59e | ||
|
|
47cee5f6a1 | ||
|
|
6820b748f7 | ||
|
|
42422845b0 | ||
|
|
8ed11cdc07 | ||
|
|
006cd1ee89 | ||
|
|
49f7ee6042 | ||
|
|
9612e4d4e9 | ||
|
|
4b938db0c1 | ||
|
|
b0a079dce9 | ||
|
|
52069f35c5 | ||
|
|
140535d875 | ||
|
|
611084d00c | ||
|
|
1b7c810eeb | ||
|
|
dd2a698147 | ||
|
|
e99df58bce | ||
|
|
70139221ab | ||
|
|
5d8d2b4a34 | ||
|
|
f59f322226 | ||
|
|
acca3f37b5 | ||
|
|
0de62f620d | ||
|
|
351c4a00e9 | ||
|
|
b048d81ac0 | ||
|
|
b9c9a2b75f | ||
|
|
4cea22d813 | ||
|
|
edad4e7d35 | ||
|
|
e28acd8d2d | ||
|
|
86feaff82f | ||
|
|
5f747db224 | ||
|
|
ccda41e18e | ||
|
|
2bb83c6532 | ||
|
|
ec59faba8c | ||
|
|
c5d8c44b81 | ||
|
|
7a4e4629e5 | ||
|
|
4d3dd95a60 | ||
|
|
713021359e | ||
|
|
927d2c36e8 | ||
|
|
afc6368915 | ||
|
|
3f604ad7b5 | ||
|
|
c5d4e237ad | ||
|
|
0b50b21779 | ||
|
|
cafc70f1c9 | ||
|
|
b4377c346f | ||
|
|
7c206af757 | ||
|
|
6212d0a052 | ||
|
|
8909822aea | ||
|
|
5fc0964f73 | ||
|
|
7725559a44 | ||
|
|
03414a58e9 | ||
|
|
ade0419bf6 | ||
|
|
9d646b2eed | ||
|
|
0d718a5ca2 | ||
|
|
78c8b36dfa | ||
|
|
b5ca0874fa | ||
|
|
e209becff9 | ||
|
|
652d42aa6b | ||
|
|
058a11597f | ||
|
|
dd73d6e19a | ||
|
|
3793f618a8 | ||
|
|
6621983a9c | ||
|
|
e0b0302f5a | ||
|
|
b02584bec6 | ||
|
|
c832d61409 | ||
|
|
ac701f28b5 | ||
|
|
53032140e7 | ||
|
|
3c7633564f | ||
|
|
43eae40404 | ||
|
|
e34b1b54d5 | ||
|
|
0658a7de0d | ||
|
|
9e0a4dfac8 | ||
|
|
82034ade90 | ||
|
|
b979203e12 | ||
|
|
c9a368f13a | ||
|
|
cfabf22fef | ||
|
|
61aef77916 | ||
|
|
8b6c6beceb | ||
|
|
4688e49454 | ||
|
|
76cc28b215 | ||
|
|
467e5423d2 | ||
|
|
422bf7b689 | ||
|
|
1d230bd4aa | ||
|
|
029850842b | ||
|
|
cc65555c3d | ||
|
|
3b090396a4 | ||
|
|
2e4656ae8b | ||
|
|
ebce63baa4 | ||
|
|
08f691ee0d | ||
|
|
7550a19951 | ||
|
|
daeb76df11 | ||
|
|
31315e5ba6 | ||
|
|
e4e1fa6a4c | ||
|
|
6e36312be8 | ||
|
|
9cc7ba1d82 | ||
|
|
9182001147 | ||
|
|
5d1510083e | ||
|
|
7035f4cc1f | ||
|
|
da826b2a22 | ||
|
|
a152d8664d | ||
|
|
6ad75d963a | ||
|
|
e92d34a9e3 | ||
|
|
768066db58 | ||
|
|
886b63e55b | ||
|
|
3c7ee65b7d | ||
|
|
0e060c4564 | ||
|
|
3e127dbd9c | ||
|
|
ad66d14a1b | ||
|
|
5db61b78ea | ||
|
|
85f9597f2c | ||
|
|
73aca22605 | ||
|
|
077343ca20 | ||
|
|
c76946c770 | ||
|
|
2344a49260 | ||
|
|
65f463dbbd | ||
|
|
6e100a70b9 | ||
|
|
77650ebe09 | ||
|
|
cdba2bd9e2 | ||
|
|
e84cc92fcd | ||
|
|
a2890948bb | ||
|
|
f2e1ebef45 | ||
|
|
3c33614115 | ||
|
|
fcc4b1e8ee | ||
|
|
c8ac4ec1e8 | ||
|
|
93ab8b88d4 | ||
|
|
cc12c302e3 | ||
|
|
73941ca30e | ||
|
|
f963711820 | ||
|
|
1a0a301fb6 | ||
|
|
f87a9b7aae | ||
|
|
f18f0bc762 | ||
|
|
c3b1bc6674 | ||
|
|
e9bb228528 | ||
|
|
a906957454 | ||
|
|
49d10337a1 | ||
|
|
87c6c31f1f | ||
|
|
5f3639e396 | ||
|
|
14d36ef8dc | ||
|
|
fc186f1718 | ||
|
|
70aa93fe8d | ||
|
|
901df22d39 | ||
|
|
cd0fe3d6d4 | ||
|
|
09f2c3f645 | ||
|
|
116542d8e4 | ||
|
|
ffae6e269b | ||
|
|
2194dc0463 | ||
|
|
63e3923349 | ||
|
|
da2d426137 | ||
|
|
8df7672624 | ||
|
|
b9dba61c97 | ||
|
|
1872a1a77c | ||
|
|
cf57bad7fa | ||
|
|
7e956894ee | ||
|
|
f78046d4c1 | ||
|
|
beb09cbf4a | ||
|
|
6b2a5d7777 | ||
|
|
4b0d368222 | ||
|
|
5ab2f6ea3a | ||
|
|
f845bf5b25 | ||
|
|
bb10d64d58 | ||
|
|
96e8e9736f | ||
|
|
62472b97e6 | ||
|
|
10b89ff00b | ||
|
|
2d8339343f | ||
|
|
9ddd88b5d0 | ||
|
|
614ec645c6 | ||
|
|
893ffc48dc | ||
|
|
7bddb70f11 | ||
|
|
9d4a38e560 | ||
|
|
9d20b31585 | ||
|
|
fb8838375f | ||
|
|
dead4e3f85 | ||
|
|
9c833a8a95 | ||
|
|
0b90794bca | ||
|
|
fd89757814 | ||
|
|
5eedc312c8 | ||
|
|
36c1cc5e0c | ||
|
|
0b4cdea9dd | ||
|
|
12172168f6 | ||
|
|
c79aa49252 | ||
|
|
b869308bf4 | ||
|
|
7a748bd142 | ||
|
|
d11669e613 | ||
|
|
b5108b938c | ||
|
|
049d510536 | ||
|
|
113f7b18a6 | ||
|
|
65b248c3cf | ||
|
|
1698996ac3 | ||
|
|
2ae47f2375 | ||
|
|
6733d12e81 | ||
|
|
8b5109e32f | ||
|
|
7b651d56b1 | ||
|
|
23b9b8aaf6 | ||
|
|
8d9fb43828 | ||
|
|
0d1be72fdb | ||
|
|
ce102a2af6 | ||
|
|
dda6071bbc | ||
|
|
0c4809bb16 | ||
|
|
4b87c5e80e | ||
|
|
b8fe9b9074 | ||
|
|
bc8b18cea8 | ||
|
|
20fdd3e891 | ||
|
|
ff9147487f | ||
|
|
af494f3b5e | ||
|
|
f68d26317d | ||
|
|
5b881f7c77 | ||
|
|
fe5efff255 | ||
|
|
46d18a642c | ||
|
|
c00012f28b | ||
|
|
d0050f7d59 | ||
|
|
f3a4881261 | ||
|
|
1728909964 | ||
|
|
f6e7460b24 | ||
|
|
351829888f | ||
|
|
e520c30ade | ||
|
|
99267ac2f0 | ||
|
|
6f1f7ef7fc | ||
|
|
d647e947e2 | ||
|
|
adef45c30f | ||
|
|
8b80859023 | ||
|
|
907fff89c8 | ||
|
|
ca532ffdaf | ||
|
|
64ee842dc4 | ||
|
|
9c0754a88d | ||
|
|
3dac9e3dae | ||
|
|
6963e667aa | ||
|
|
af292b291b | ||
|
|
918f75704e | ||
|
|
6e258e5d0c | ||
|
|
a8ea4ec085 | ||
|
|
2f894af028 | ||
|
|
dd400e1214 | ||
|
|
f8a183bfdf | ||
|
|
2404193b61 | ||
|
|
3ef11f5b26 | ||
|
|
30419cec12 | ||
|
|
162841feb8 | ||
|
|
f910f63f8d | ||
|
|
860cb75f8f | ||
|
|
293be93b93 | ||
|
|
976e77b31e | ||
|
|
9f28ee2982 | ||
|
|
e6c1e48b86 | ||
|
|
2c2880199d | ||
|
|
c7f8737eeb | ||
|
|
f3aea3108d | ||
|
|
e4f8ad0f05 | ||
|
|
8b80f72313 | ||
|
|
06ebce31ca | ||
|
|
59db8ba997 | ||
|
|
df37d4aa7f | ||
|
|
fd9935c800 | ||
|
|
c057430488 | ||
|
|
767fa78e22 | ||
|
|
a0e289dcd6 | ||
|
|
c62d147254 | ||
|
|
f739bee353 | ||
|
|
db4dfb69fb | ||
|
|
9729a5ef16 | ||
|
|
6f12818352 | ||
|
|
75fc318da0 | ||
|
|
acf71cc2c2 | ||
|
|
42efe10473 | ||
|
|
b77e72880b | ||
|
|
297c1524b4 | ||
|
|
6140743769 | ||
|
|
d5ca700828 | ||
|
|
a7e1fe76c3 | ||
|
|
f6c55279d1 | ||
|
|
826f4835bb | ||
|
|
119a28def1 | ||
|
|
c1ffb8bc33 | ||
|
|
60cc9d14af | ||
|
|
7cf4f8fdaa | ||
|
|
1b84e83061 | ||
|
|
b0d2ee8760 | ||
|
|
2022432d35 | ||
|
|
c0ed335d84 | ||
|
|
4964562866 | ||
|
|
f78dc443ad | ||
|
|
56b47db778 | ||
|
|
2d4bf17b28 | ||
|
|
3b1819c68d | ||
|
|
575bbdb53b | ||
|
|
2fa7482028 | ||
|
|
e5062317e2 | ||
|
|
543f4fa3c2 | ||
|
|
49cdf69815 | ||
|
|
04196a1a19 | ||
|
|
b8622b8708 | ||
|
|
a2feca0ba3 | ||
|
|
ae6c6a75b2 | ||
|
|
bd987e9531 | ||
|
|
24d69fd820 | ||
|
|
9e2fa8d7f1 | ||
|
|
74ba2f7283 | ||
|
|
7603b5c63f | ||
|
|
03566afe66 | ||
|
|
3c2e314e2d | ||
|
|
1fcf08b5cd | ||
|
|
c13db89ed4 | ||
|
|
e68ae9e8a6 | ||
|
|
fb252d6db3 | ||
|
|
df959353d6 | ||
|
|
fbc443483d | ||
|
|
00f1dbc3dd | ||
|
|
c6021ff012 | ||
|
|
7a8cfcee35 | ||
|
|
c15586a1c4 | ||
|
|
be1d124e78 | ||
|
|
6fabf84d05 | ||
|
|
3299275da2 | ||
|
|
5539243bf3 | ||
|
|
c3d330f500 | ||
|
|
8c9e886136 | ||
|
|
cfde9944f8 | ||
|
|
b35d778abe | ||
|
|
1e9ee81df3 | ||
|
|
545d5730b6 | ||
|
|
28e645f9ca | ||
|
|
5b5c8a4beb | ||
|
|
6178be974a | ||
|
|
7771c3159b | ||
|
|
fc1e9c5d71 | ||
|
|
359de5b85e | ||
|
|
68dc4904cf | ||
|
|
057fd02e26 | ||
|
|
5c86ef6682 | ||
|
|
7c14bd799f | ||
|
|
edac046fcd | ||
|
|
8b23b386f7 | ||
|
|
e9bed59032 | ||
|
|
54077be32c | ||
|
|
30ad442dd1 | ||
|
|
b0c677d5a7 | ||
|
|
abe1b93e3c | ||
|
|
fd699cbb73 | ||
|
|
6f93ca11de | ||
|
|
916663b493 | ||
|
|
dbf2cddd87 | ||
|
|
d3c248cf5c | ||
|
|
bd93a94bb7 | ||
|
|
a46836e2a6 | ||
|
|
a1d88b4faa | ||
|
|
29741c0ec8 | ||
|
|
0b5fb92452 | ||
|
|
65782e87c1 | ||
|
|
6403df3330 | ||
|
|
c6a8f5305a | ||
|
|
d354e63267 | ||
|
|
836b174d15 | ||
|
|
4435f25ee6 | ||
|
|
4ab4bf4a3a | ||
|
|
d2f1d3c86c | ||
|
|
98257de005 | ||
|
|
5ea5c6ff7d | ||
|
|
441324d354 | ||
|
|
44f093b2fa | ||
|
|
38a16d1ae2 | ||
|
|
6b9de40442 | ||
|
|
d9c9eb9799 | ||
|
|
e88c8eee0f | ||
|
|
a6ca843ced | ||
|
|
6967a5cc14 | ||
|
|
e0ebf70907 | ||
|
|
53d0202f3b | ||
|
|
8daf38654d | ||
|
|
17337440f2 | ||
|
|
8e5e79a799 | ||
|
|
5c1cf14303 | ||
|
|
f2ad796104 | ||
|
|
886df69058 | ||
|
|
703aa95e60 | ||
|
|
7919e14d90 | ||
|
|
c8ef191660 | ||
|
|
840b656d1c | ||
|
|
d28516b6b1 | ||
|
|
5797bf549c | ||
|
|
e1ea721ea9 | ||
|
|
5ed1284baf | ||
|
|
e507023a03 | ||
|
|
2b328b9bb2 | ||
|
|
d155e13399 | ||
|
|
6993d4782d | ||
|
|
9267efce01 | ||
|
|
8c88cec863 | ||
|
|
b4b9976567 | ||
|
|
3cbd820d9c | ||
|
|
edd77b0222 | ||
|
|
c0991df9a2 | ||
|
|
26921c899e | ||
|
|
a1e5912d37 | ||
|
|
037f1ec887 | ||
|
|
89ab44590b | ||
|
|
4a20be8b34 | ||
|
|
32a401ba67 | ||
|
|
138c01aef8 | ||
|
|
3e54bc9c77 | ||
|
|
9966fc4651 | ||
|
|
c9ed5ec98f | ||
|
|
1f4ae5dbfb | ||
|
|
d0d3cc8faa | ||
|
|
7513ae28f0 | ||
|
|
c151c6f55a | ||
|
|
d491aeeba6 | ||
|
|
ab93d0acea | ||
|
|
f40f3f934f | ||
|
|
4d57adb393 | ||
|
|
e91db18677 | ||
|
|
9a88d65ff7 | ||
|
|
211d8c7f19 | ||
|
|
763cdca114 | ||
|
|
0d56d35646 | ||
|
|
d1e80cb86a | ||
|
|
2e116489c8 | ||
|
|
a94daa1216 | ||
|
|
ad57d665cc | ||
|
|
db2c42f0f4 | ||
|
|
b713cf86c7 | ||
|
|
11d02ccf44 | ||
|
|
54f3c8beac | ||
|
|
2ed51bf1f3 | ||
|
|
9f859f024c | ||
|
|
d47175df8a | ||
|
|
d6a80d36b1 | ||
|
|
a857e3f31e | ||
|
|
918dfb05cd | ||
|
|
f22e0c4384 | ||
|
|
daa08626ae | ||
|
|
45592e6acd | ||
|
|
6944eb3392 | ||
|
|
85f20aca58 | ||
|
|
ca8d8b3587 | ||
|
|
74921a0686 | ||
|
|
be4296f288 | ||
|
|
836de56276 | ||
|
|
ef60ac3836 | ||
|
|
bdd1ba05e8 | ||
|
|
f0a22d5517 | ||
|
|
417c8d7874 | ||
|
|
5a111cdb2a | ||
|
|
ef2f779f71 | ||
|
|
f0fd0db7bb | ||
|
|
b4ab45d2e7 | ||
|
|
73c4bcee30 | ||
|
|
d4daf098e1 | ||
|
|
95de1fff69 | ||
|
|
96480b4382 | ||
|
|
14ae553557 | ||
|
|
4de809e275 | ||
|
|
a383382529 | ||
|
|
d2b3061ed9 | ||
|
|
073b10929a | ||
|
|
6a2cfcab34 | ||
|
|
00c85ae7d3 | ||
|
|
d1b1e88389 | ||
|
|
5679ff5df9 | ||
|
|
3f28b39da0 | ||
|
|
ee21536742 | ||
|
|
6fafeedeb9 | ||
|
|
d86c53858e | ||
|
|
0a22f80942 | ||
|
|
fb247197e8 | ||
|
|
261ed05fa3 | ||
|
|
f5ce48a7c2 | ||
|
|
82d128974c | ||
|
|
34ae731959 | ||
|
|
030a92d048 | ||
|
|
a327358b7a | ||
|
|
9bd078d3e9 | ||
|
|
412405e22c | ||
|
|
2b20b34a74 | ||
|
|
f269a07463 | ||
|
|
d9948e8ee0 | ||
|
|
ddb2fe4b03 | ||
|
|
471f7184fb | ||
|
|
92ea5da358 | ||
|
|
09f1d3ca15 | ||
|
|
3729b8eac2 | ||
|
|
ca5f345414 | ||
|
|
15cf1e8a53 | ||
|
|
da74b0ece1 | ||
|
|
f50fe6f22f | ||
|
|
542eec2a9e | ||
|
|
4c5460fc9e | ||
|
|
394b8f1ce6 | ||
|
|
abeeda1a2b | ||
|
|
9e6ea3f486 | ||
|
|
2777a0946c | ||
|
|
7f43c2659a | ||
|
|
a4fa11c881 | ||
|
|
8230e51c05 | ||
|
|
6e62d4791c | ||
|
|
ce8abdb8cd | ||
|
|
941bb7edaa | ||
|
|
1acbdf8b9e | ||
|
|
530804c847 | ||
|
|
b00b426e9c | ||
|
|
128f1753c0 | ||
|
|
1922629d27 | ||
|
|
283a03a7cc | ||
|
|
cdab26a97b | ||
|
|
5d3311c46b | ||
|
|
7cbc21671b | ||
|
|
d14d820e25 | ||
|
|
b567d02df2 | ||
|
|
dbb58541f2 | ||
|
|
5a42caf4f8 | ||
|
|
c2acd010ce | ||
|
|
4a8bcc6f71 | ||
|
|
89e4dc7a08 | ||
|
|
c344eedb12 | ||
|
|
0aeb1a62b9 | ||
|
|
316e0f5a68 | ||
|
|
6f9314f76f | ||
|
|
6ef21795ba | ||
|
|
e44855493e | ||
|
|
101a3c8af5 | ||
|
|
8fbbbce4ec | ||
|
|
1de8bb850f | ||
|
|
25d09186fe | ||
|
|
9552b7c6f9 | ||
|
|
3d692e4198 | ||
|
|
215ca9f755 | ||
|
|
7dbcb24f3d | ||
|
|
18b4bd62f7 | ||
|
|
20f3cf5327 | ||
|
|
c1d35e8af5 | ||
|
|
e1cd318f61 | ||
|
|
4da0952091 | ||
|
|
a60f75f0a9 | ||
|
|
3814009ad9 | ||
|
|
fe7bb00112 | ||
|
|
2356753dfc | ||
|
|
bd23ec268b | ||
|
|
1d5f23c3c7 | ||
|
|
f424bf53b3 | ||
|
|
efbdf0006e | ||
|
|
625b173ee4 | ||
|
|
0a8d9bc3d3 | ||
|
|
0d0909a461 | ||
|
|
ebb0a538f8 | ||
|
|
337a566617 | ||
|
|
fecee7a12b | ||
|
|
282ec99ce8 | ||
|
|
17a4985ebe | ||
|
|
13b17d91a9 | ||
|
|
5782005007 | ||
|
|
258f2dbe8f | ||
|
|
e7b35bd0c2 | ||
|
|
176bd229e6 | ||
|
|
c12f1251b0 | ||
|
|
0710736661 | ||
|
|
51ebe520f4 | ||
|
|
1ef566c824 | ||
|
|
96cf06fe8d | ||
|
|
38ebfdcd0c | ||
|
|
b5649e2a6f | ||
|
|
5fc6dadeae | ||
|
|
22cfec9276 | ||
|
|
0a82ec5f0a | ||
|
|
3bdd1f6c4f | ||
|
|
1e466dc1fe | ||
|
|
3b302d2f86 | ||
|
|
7f108eebf9 | ||
|
|
9d41fee2fe | ||
|
|
bef686f95a | ||
|
|
4bdcb974bd | ||
|
|
af042bd23c | ||
|
|
19e3cef7dd | ||
|
|
085ab4bfb4 | ||
|
|
f7bd03d051 | ||
|
|
b48d751717 | ||
|
|
92a4a1b210 | ||
|
|
6a57fcc04a | ||
|
|
749e11762f | ||
|
|
61817726c3 | ||
|
|
808354e969 | ||
|
|
d2f594ff4a | ||
|
|
fa18467cdd | ||
|
|
4c940d02ef | ||
|
|
04202a6b70 | ||
|
|
4483901270 | ||
|
|
f02d894697 | ||
|
|
2bc130856a | ||
|
|
aa3a4dca65 | ||
|
|
4f65931f51 | ||
|
|
93be61e483 | ||
|
|
cb7fe364bc | ||
|
|
9cdcb4b22c | ||
|
|
c49072f9b6 | ||
|
|
61df26b667 | ||
|
|
25c935f1db | ||
|
|
6b982bc0c7 | ||
|
|
caccc803d3 | ||
|
|
5be640632b | ||
|
|
40912f0ffd | ||
|
|
d518860a34 | ||
|
|
185617ed9e | ||
|
|
5ba96b627e | ||
|
|
edf955f4cd | ||
|
|
8d0b04668e | ||
|
|
a72e5e02da | ||
|
|
4740404c2e | ||
|
|
34253916ea | ||
|
|
1d77ba2694 | ||
|
|
765041c587 | ||
|
|
8c6bca7666 | ||
|
|
c1b1cef590 | ||
|
|
507f7804c3 | ||
|
|
453bacf9bd | ||
|
|
25778458c6 | ||
|
|
7a42646bed | ||
|
|
755b7034d2 | ||
|
|
122fcfd701 | ||
|
|
2c905eddc2 | ||
|
|
3e8eb9abb5 | ||
|
|
d2df3b2d9a | ||
|
|
2a0c983c2e | ||
|
|
1319432fde | ||
|
|
02e2aeb6d1 | ||
|
|
5e35edd976 | ||
|
|
4d63c6266c | ||
|
|
48f8d41993 | ||
|
|
0b41ccad83 | ||
|
|
f994afbc11 | ||
|
|
5dea783677 | ||
|
|
347ef9a1d0 | ||
|
|
739aa5311c | ||
|
|
805018286c | ||
|
|
45f2a47ba5 | ||
|
|
fe503b7bee | ||
|
|
6736570ee7 | ||
|
|
88c06eea6e | ||
|
|
13693ef9e5 | ||
|
|
3a54326e73 | ||
|
|
941c1c8a45 | ||
|
|
f56e102f20 | ||
|
|
010c669ce2 | ||
|
|
d328058075 | ||
|
|
9609e7b297 | ||
|
|
92718372b8 | ||
|
|
9108eb5616 | ||
|
|
354ad9d0d5 | ||
|
|
100b92036b | ||
|
|
e9ed1ecc8c | ||
|
|
92595ccc35 | ||
|
|
ea45044dc7 | ||
|
|
1ef28494b3 | ||
|
|
985d324593 | ||
|
|
96e7178cd2 | ||
|
|
f41e2358a9 | ||
|
|
0f146ff355 | ||
|
|
465f241e89 | ||
|
|
2bdb011240 | ||
|
|
dd55725f9b | ||
|
|
6b16808207 | ||
|
|
411111fa55 | ||
|
|
5345a12aca | ||
|
|
39adcde16d | ||
|
|
3c37ee60af | ||
|
|
4effc4dd60 | ||
|
|
fec9fa17e8 | ||
|
|
4071b285ce | ||
|
|
fca306ffa6 | ||
|
|
0e042925f2 | ||
|
|
99421846c7 | ||
|
|
592885ff6c | ||
|
|
a95306c317 | ||
|
|
8bdbf7b994 | ||
|
|
7bc80f0814 | ||
|
|
aed65d2594 | ||
|
|
63b3e4d90d | ||
|
|
f9c459eab2 | ||
|
|
a6d83149a4 | ||
|
|
387b477034 | ||
|
|
68cdcd193c | ||
|
|
08438c7725 | ||
|
|
1894b46d3a | ||
|
|
367a825955 | ||
|
|
be799eb254 | ||
|
|
4820a27016 | ||
|
|
6b57202421 | ||
|
|
4e82212956 | ||
|
|
6b0f5ed12d | ||
|
|
0a258caf48 | ||
|
|
f546a68f7c | ||
|
|
1e5bab09db | ||
|
|
35fa18a852 | ||
|
|
90c1eeb312 | ||
|
|
404c32b49b | ||
|
|
c0f4cd2097 | ||
|
|
fa98908dea | ||
|
|
23e8567e86 | ||
|
|
ca3edb6687 | ||
|
|
a02e28f493 | ||
|
|
7428f87899 | ||
|
|
d6a7e32ca9 | ||
|
|
df0f562817 | ||
|
|
0dea2bc79e | ||
|
|
02481560a9 | ||
|
|
dc1d1015ed | ||
|
|
59fffbd3ee | ||
|
|
655cbca574 | ||
|
|
e22c28a3c9 | ||
|
|
e823e371f6 | ||
|
|
feae7d6269 | ||
|
|
e1c0ea0720 | ||
|
|
4e1a389c8b | ||
|
|
0362a6a3dc | ||
|
|
3891dea009 | ||
|
|
2d66063763 | ||
|
|
1ccb7e2092 | ||
|
|
62ad99d0b6 | ||
|
|
66d07fbed3 | ||
|
|
ec117adb2d | ||
|
|
3aff31b826 | ||
|
|
1621944105 | ||
|
|
16d0d11e0b | ||
|
|
9158e24295 | ||
|
|
0b81f28e99 | ||
|
|
2ccb814223 | ||
|
|
f213abd70a | ||
|
|
b87eddeeda | ||
|
|
92fc56e70d | ||
|
|
e22f3238fb | ||
|
|
c597c4c234 | ||
|
|
5973c3d1a0 | ||
|
|
118e177756 | ||
|
|
d50472cd7b | ||
|
|
11604bbcfd | ||
|
|
cf428598d5 | ||
|
|
a45fc0c4df | ||
|
|
88ac82a013 | ||
|
|
5abf0df154 | ||
|
|
9a3b458c45 | ||
|
|
1b2de37c4e | ||
|
|
f7338bf00e | ||
|
|
bad7be39a6 | ||
|
|
40459defa4 | ||
|
|
b7de4b81a6 | ||
|
|
563695cdfa | ||
|
|
fd30112ee8 | ||
|
|
a36794b98b | ||
|
|
e52653469d | ||
|
|
b61035a3a3 | ||
|
|
b3039e71be | ||
|
|
c025013ca8 | ||
|
|
2c614f3bb6 | ||
|
|
07a27b25e0 | ||
|
|
4c080572ac | ||
|
|
95cd6ea48e | ||
|
|
2e3c57226f | ||
|
|
69f7367a18 | ||
|
|
8ff7f0bf5e | ||
|
|
e2a9470f31 | ||
|
|
a2a64e73bc | ||
|
|
15305e3a0b | ||
|
|
bcb484ba62 | ||
|
|
278fd8023b | ||
|
|
ba7efb45e7 | ||
|
|
8d3d218067 | ||
|
|
5185934f25 | ||
|
|
0a9cbfd0e5 | ||
|
|
35e237b5b3 | ||
|
|
ba18cd505b | ||
|
|
662df53a10 | ||
|
|
eadf2e89a8 | ||
|
|
a3b54b9891 | ||
|
|
18cab2c759 | ||
|
|
8edc4fd67a | ||
|
|
b914163499 | ||
|
|
c55c8bff6e | ||
|
|
e669bb1f82 | ||
|
|
452c3fd355 | ||
|
|
60be0c92da | ||
|
|
03bd88a2d5 | ||
|
|
7f355698bc | ||
|
|
22ec675cec | ||
|
|
3954c482ce | ||
|
|
d06feec37b | ||
|
|
536af96ccc | ||
|
|
e1a8d3db54 | ||
|
|
83d51a85ce | ||
|
|
4400744f35 | ||
|
|
ea4b85ad54 | ||
|
|
60b6a9ce47 | ||
|
|
927e00bd66 | ||
|
|
cd8d2dd8f3 | ||
|
|
6861965779 | ||
|
|
8a9a1d7df7 | ||
|
|
e7e66699b1 | ||
|
|
681321feeb | ||
|
|
a637b6d4d0 | ||
|
|
97026746f8 | ||
|
|
5161ade786 | ||
|
|
6dd62a164e | ||
|
|
3bece7599c | ||
|
|
b2f3a53980 | ||
|
|
cea31a8573 | ||
|
|
ce6647a84a | ||
|
|
d54ba9a5c6 | ||
|
|
1e18d913e0 | ||
|
|
ef8c47a49f | ||
|
|
0041c7d3b3 | ||
|
|
f6cca5cb35 | ||
|
|
fa5abbf96f | ||
|
|
2ff365765d | ||
|
|
ee31e4a411 | ||
|
|
ee4068d671 | ||
|
|
5e86c95db6 | ||
|
|
6126624631 | ||
|
|
a320e4d48d | ||
|
|
a7c5841e76 | ||
|
|
0f242a94b4 | ||
|
|
8f8a0897e4 | ||
|
|
7c6b64123c | ||
|
|
55642093ed | ||
|
|
5660f40512 | ||
|
|
3259b7183b | ||
|
|
df7cd648ac | ||
|
|
2a5713ed67 | ||
|
|
b7c36c07c6 | ||
|
|
de93281f3e | ||
|
|
86fbdced1b | ||
|
|
43bcfb9a4e | ||
|
|
0daf6af3b8 | ||
|
|
c3bccbade8 | ||
|
|
3261628da4 | ||
|
|
1e39f48235 | ||
|
|
05b5d9da9b | ||
|
|
3491dde94a | ||
|
|
db2aa920e2 | ||
|
|
8067b2e634 | ||
|
|
273c6d7dee | ||
|
|
4d0149a4a0 | ||
|
|
d9f3db5bf3 | ||
|
|
c8a679ecce | ||
|
|
3abe47ae9e | ||
|
|
725a8dc237 | ||
|
|
fcb05a7834 | ||
|
|
02e22df4d5 | ||
|
|
694c0af146 | ||
|
|
8bfca2f10a | ||
|
|
6bea9a087c | ||
|
|
3f4218f75f | ||
|
|
3849f59126 | ||
|
|
239b12cbb0 | ||
|
|
8ccee87378 | ||
|
|
9e518ffc4c | ||
|
|
e3233fd19f | ||
|
|
d3af9b0f14 | ||
|
|
08daca848b | ||
|
|
c5da8e3b10 | ||
|
|
3b0ffde1fb | ||
|
|
6ca97e8d5d | ||
|
|
34a727b435 | ||
|
|
6a9c865fec | ||
|
|
b1b9100600 | ||
|
|
bda0943d58 | ||
|
|
ad6db71f7e | ||
|
|
b21d202c7f | ||
|
|
3ea8ea1e3b | ||
|
|
ee897a9973 | ||
|
|
bb9ea3f60f | ||
|
|
5f8cfb9977 | ||
|
|
d7928a70bf | ||
|
|
93568af5a9 | ||
|
|
9e86d97253 | ||
|
|
84bac0c462 | ||
|
|
3c18a258ff | ||
|
|
9759c68d58 | ||
|
|
f57a190405 | ||
|
|
d2eb203aa1 | ||
|
|
dd2c2dbe61 | ||
|
|
2529592b66 | ||
|
|
f248d2fe4e | ||
|
|
875bcd8ce2 | ||
|
|
726f9375e1 | ||
|
|
0c2e200245 | ||
|
|
a10db462eb | ||
|
|
6df28d8171 | ||
|
|
8367f15638 | ||
|
|
69456d3569 | ||
|
|
3b644338bc | ||
|
|
02e29aa22f | ||
|
|
88c489a585 | ||
|
|
706e73210d | ||
|
|
d52809c914 | ||
|
|
6058286577 | ||
|
|
a271f24157 | ||
|
|
2299375aaa | ||
|
|
c3290526a4 | ||
|
|
a9d871e9af | ||
|
|
5eb25796ca | ||
|
|
5d650bd276 | ||
|
|
b6be519537 | ||
|
|
160477d612 | ||
|
|
4a89403c37 | ||
|
|
e54cfa629e | ||
|
|
b2bc09be16 | ||
|
|
2f5d1cebb0 | ||
|
|
e4d144651e | ||
|
|
ce56dfc239 | ||
|
|
ad4f8fd7a0 | ||
|
|
c4070cb603 | ||
|
|
75c9322f75 | ||
|
|
602fd7d658 | ||
|
|
818fd2fb43 | ||
|
|
e4912fcb09 | ||
|
|
3692ec49c1 | ||
|
|
9a1ea3f375 | ||
|
|
10a754642a | ||
|
|
bc9de5d094 | ||
|
|
9f93476167 | ||
|
|
f61492d353 | ||
|
|
50cf67546b | ||
|
|
41d83ec421 | ||
|
|
cc9377b37d | ||
|
|
7f986d5f60 | ||
|
|
d272bf8a29 | ||
|
|
ba5260f5b1 | ||
|
|
40c788fc33 | ||
|
|
7eca15d50f | ||
|
|
8f147e7de2 | ||
|
|
85358c7dd4 | ||
|
|
87c7eafb87 | ||
|
|
613f4848da | ||
|
|
3cb06e4819 | ||
|
|
811701ef7f | ||
|
|
c14dc67d8a | ||
|
|
867767f009 | ||
|
|
640216d076 | ||
|
|
3a1f378e2d | ||
|
|
6b7c61f228 | ||
|
|
28ba2c00fc | ||
|
|
b56daae321 | ||
|
|
b57450398c | ||
|
|
0c25cf0ec5 | ||
|
|
c3d884e895 | ||
|
|
7a21f96552 | ||
|
|
55cfbb721b | ||
|
|
4ccb4c636f | ||
|
|
0d5c3a3a0c | ||
|
|
6bc1507114 | ||
|
|
b093cbff15 | ||
|
|
30cb8ec9c3 | ||
|
|
d5e51d6c38 | ||
|
|
6f0a6c7f69 | ||
|
|
549d71073e | ||
|
|
4ad5522f9e | ||
|
|
2145c1a48c | ||
|
|
0f093b1238 | ||
|
|
1aa3fd7056 | ||
|
|
cc4c47c3ee | ||
|
|
6e3f3fac9d | ||
|
|
261adefbc7 | ||
|
|
7eafcfd7ad | ||
|
|
6183d6a22e | ||
|
|
51d42692ba | ||
|
|
18a9c059f4 | ||
|
|
50a13ffd6d | ||
|
|
b360944742 | ||
|
|
59d1a47846 | ||
|
|
22969d8daa | ||
|
|
95ba87945b | ||
|
|
63aeee8b22 | ||
|
|
da11a86f99 | ||
|
|
98c2f012ee | ||
|
|
386c6144f8 | ||
|
|
666f9c2db9 | ||
|
|
f7c49588fb | ||
|
|
c0e6f06a5c | ||
|
|
6668c8ff66 | ||
|
|
31907f2d16 | ||
|
|
452d0af8c9 | ||
|
|
c5a28f3601 | ||
|
|
d13f26dc7e | ||
|
|
03374483e4 | ||
|
|
b1c0a865a0 | ||
|
|
7b0799a6f6 | ||
|
|
0de2976933 | ||
|
|
20c7bf3c48 | ||
|
|
55e090ba2a | ||
|
|
ded326c822 | ||
|
|
1ee9c2cf0e | ||
|
|
e41d9008b3 | ||
|
|
1ec24251a2 | ||
|
|
2be48c3847 | ||
|
|
2137b79f6d | ||
|
|
0b8086bd36 | ||
|
|
d16659890c | ||
|
|
076d150f72 | ||
|
|
62deffd017 | ||
|
|
f1ec70ff14 | ||
|
|
af27c763e0 | ||
|
|
7336417634 | ||
|
|
cde46c0889 | ||
|
|
5da4dadc31 | ||
|
|
e32ca089ec | ||
|
|
8d2f644177 | ||
|
|
a003010be5 | ||
|
|
89ada83012 | ||
|
|
65fbf6926c | ||
|
|
5e652364c9 | ||
|
|
8b7c3d6cfa | ||
|
|
3b214a0105 | ||
|
|
c0c4073bc4 | ||
|
|
dbe7324c7f | ||
|
|
9316255e87 | ||
|
|
7f7dff7019 | ||
|
|
961e004a49 | ||
|
|
02c1e4fb65 | ||
|
|
bcbc2a6e95 | ||
|
|
0c749a126c | ||
|
|
3698ebad7c | ||
|
|
f59a5c90a5 | ||
|
|
36e4a69891 | ||
|
|
19b560f7c1 | ||
|
|
0bec92e87e | ||
|
|
a0d02f16cd | ||
|
|
4a0f655e1c | ||
|
|
91bf65292d | ||
|
|
a69d52d688 | ||
|
|
79fe224cea | ||
|
|
757d8e59a2 | ||
|
|
53ec1416b4 | ||
|
|
e5add21612 | ||
|
|
d1b52534de | ||
|
|
35edc053b5 | ||
|
|
2aa8d3be0f | ||
|
|
ec0a5377b3 | ||
|
|
b876da069d | ||
|
|
f06063d7eb | ||
|
|
c6ba4f4bee | ||
|
|
89da4ab2a1 | ||
|
|
99ac0a3cab | ||
|
|
a964c30705 | ||
|
|
2334d0297d | ||
|
|
e444c6bcf0 | ||
|
|
601582fc98 | ||
|
|
e939dbe24e | ||
|
|
143ad85fd5 | ||
|
|
20377e9789 | ||
|
|
e4a24df7b4 | ||
|
|
e88ca00658 | ||
|
|
2312c612d7 | ||
|
|
3aee52482d | ||
|
|
32248e0be6 | ||
|
|
6c18c320bd | ||
|
|
e31f32e5aa | ||
|
|
a856390566 | ||
|
|
64b8291487 | ||
|
|
2ebd1310c9 | ||
|
|
09118fcb42 | ||
|
|
5d5167abbc | ||
|
|
bf43dea060 | ||
|
|
6ebab830c5 | ||
|
|
9a9a78e0bd | ||
|
|
418eff7d21 | ||
|
|
d39869ca8e | ||
|
|
7829168ea7 | ||
|
|
dd83e3ee67 | ||
|
|
c3ac0e365d | ||
|
|
2986625605 | ||
|
|
8beaeabf09 | ||
|
|
fa9b4b6112 | ||
|
|
a20c2aa996 | ||
|
|
d81fbe4585 | ||
|
|
536c044139 | ||
|
|
376531195e | ||
|
|
abf4ff71d7 | ||
|
|
d25debcea3 | ||
|
|
948c186cb5 | ||
|
|
ba27e70fe3 | ||
|
|
c93d2576af | ||
|
|
e0b0156640 | ||
|
|
a3ca6a3071 | ||
|
|
b20157450b | ||
|
|
7e4f0923d7 | ||
|
|
f8c1fde358 | ||
|
|
e0c2b2982f | ||
|
|
9a231c28af | ||
|
|
94a02b7bf9 | ||
|
|
22c8fb411a | ||
|
|
cf46210ff8 | ||
|
|
f32d988297 | ||
|
|
7fe4577158 | ||
|
|
8985dcb4c0 | ||
|
|
d346444c51 | ||
|
|
113658f3f7 | ||
|
|
ef27a0d6b0 | ||
|
|
627158cb5d | ||
|
|
648edcabba | ||
|
|
0f34c6efc5 | ||
|
|
cc3cc55269 | ||
|
|
a503a9d5bc | ||
|
|
789baf99ff | ||
|
|
036279a54c | ||
|
|
481f18cf1c | ||
|
|
c54a48865b | ||
|
|
2f1e386554 | ||
|
|
1e0c7cf43d | ||
|
|
7978c89731 | ||
|
|
82221f54c5 | ||
|
|
2e23e6e4d5 | ||
|
|
4c4e94dba0 | ||
|
|
3a79fa074a | ||
|
|
696c63c6d2 | ||
|
|
8f6dc52430 | ||
|
|
c8a9ad9807 | ||
|
|
a0933d07d8 | ||
|
|
6bf61e2008 | ||
|
|
36ecc55578 | ||
|
|
47065299ca | ||
|
|
0f707a7607 | ||
|
|
7590c5550f | ||
|
|
3d23a5c79a | ||
|
|
9aa0374dc2 | ||
|
|
058a191673 | ||
|
|
ec142cbbe1 | ||
|
|
5fbb9160f1 | ||
|
|
2c3dad0592 | ||
|
|
00f039d31e | ||
|
|
497ef1750b | ||
|
|
7d4cd04ce9 | ||
|
|
04c0b9d37d | ||
|
|
b9c16c0727 | ||
|
|
702ff964fe | ||
|
|
0a30f2b7e8 | ||
|
|
dbb1d53202 | ||
|
|
2c88f36e3d | ||
|
|
d91dcad774 | ||
|
|
919439977c | ||
|
|
6eb4ea2a2d | ||
|
|
ff6187c336 | ||
|
|
1253dfe0c8 | ||
|
|
c1f2f9a970 | ||
|
|
8d5e7ed69f | ||
|
|
3d3540f090 | ||
|
|
61f1fcb99c | ||
|
|
a6478c5146 | ||
|
|
c7b576d6d3 | ||
|
|
ab3b1d5e46 | ||
|
|
ddff3faba9 | ||
|
|
f7bb8444da | ||
|
|
92128974bb | ||
|
|
524c4ab5a1 | ||
|
|
4eb15d4806 | ||
|
|
39cb3780c8 | ||
|
|
3c795eec7c | ||
|
|
81030f468b | ||
|
|
2032cb1ace | ||
|
|
5e4c91440e | ||
|
|
13bbad863f | ||
|
|
3065ed5094 | ||
|
|
9eb75cfff0 | ||
|
|
9305c99577 | ||
|
|
9078da6937 | ||
|
|
4c579639b9 | ||
|
|
b86472ab52 | ||
|
|
b9e352cbcb | ||
|
|
5e1ac485a0 | ||
|
|
527c1a12f4 | ||
|
|
ef4e99b3a7 | ||
|
|
12a9635309 | ||
|
|
78adb24a75 | ||
|
|
49955cf642 | ||
|
|
af3b289331 | ||
|
|
d11591e5aa | ||
|
|
e23744a0d0 | ||
|
|
9c6174e3f2 | ||
|
|
09c2f74d52 | ||
|
|
7d7e0254cb | ||
|
|
fe767afe9c | ||
|
|
3378f0e4ee | ||
|
|
626528fa5e | ||
|
|
c0341798ea | ||
|
|
fc114dc5db | ||
|
|
7107d844e1 | ||
|
|
59af0a817e | ||
|
|
9615eded85 | ||
|
|
c51fa23fcb | ||
|
|
8737fd6f1e | ||
|
|
0f109ab93a | ||
|
|
63df2742db | ||
|
|
7b7811f8ad | ||
|
|
80f74b9939 | ||
|
|
274cde1c21 | ||
|
|
1f29f77f66 | ||
|
|
dd7da5f354 | ||
|
|
49b30da697 | ||
|
|
90ed767d75 | ||
|
|
4c8cb31a6f | ||
|
|
7871f0f3ce | ||
|
|
a60cf40a3c | ||
|
|
f4eb2f6726 | ||
|
|
cfe87a802e | ||
|
|
6c90ec812f | ||
|
|
196d611c1c | ||
|
|
ff41cb2735 | ||
|
|
fb11674301 | ||
|
|
b9e7f4530c | ||
|
|
27049687bf | ||
|
|
13503edc63 | ||
|
|
bea89be67b | ||
|
|
d33f82d969 | ||
|
|
177632eee0 | ||
|
|
ca0de5258e | ||
|
|
1de788bc26 | ||
|
|
2b41909d47 | ||
|
|
e23a9f22dd | ||
|
|
4cb17de82a | ||
|
|
465b7850b7 | ||
|
|
a0f2eedd7f | ||
|
|
da3a4d5855 | ||
|
|
8605e3ca5a | ||
|
|
dd893e6d48 | ||
|
|
c4cd1a5a54 | ||
|
|
94152651fc | ||
|
|
563ea76192 | ||
|
|
4913fab0b3 | ||
|
|
b49d011edf | ||
|
|
b0c5d1e991 | ||
|
|
6e04281b03 | ||
|
|
f2df8e96db | ||
|
|
c6dd7605b2 | ||
|
|
71dd00da0f | ||
|
|
231fb80c2c | ||
|
|
da48ca884d | ||
|
|
8c6c66fb11 | ||
|
|
f333b57310 | ||
|
|
d1d00e6c98 | ||
|
|
52300e680a | ||
|
|
c49af06e57 | ||
|
|
b3f7353582 | ||
|
|
7db6b82a1a | ||
|
|
a50a13374f | ||
|
|
3952fe5a72 | ||
|
|
2e61a43e4f | ||
|
|
ebe03e9310 | ||
|
|
11dd3ce110 | ||
|
|
1919c24959 | ||
|
|
aa80f31e52 | ||
|
|
6d8400e72f | ||
|
|
fa8d0c91fc | ||
|
|
e91ff95712 | ||
|
|
0883a8dbff | ||
|
|
0db297d1cd | ||
|
|
db73052ee5 | ||
|
|
8b95069610 | ||
|
|
2a12744cd5 | ||
|
|
1df4059284 | ||
|
|
475894b680 | ||
|
|
1663bf8e52 | ||
|
|
ffca1d2486 | ||
|
|
eb876845ff | ||
|
|
02c134bf4b | ||
|
|
af55c11aa0 | ||
|
|
33bc6adcb5 | ||
|
|
56e4dcc11e | ||
|
|
467cf7620e | ||
|
|
85ac8dd701 | ||
|
|
1f2ad4e884 | ||
|
|
cf2d9af664 | ||
|
|
7a105047e9 | ||
|
|
bc8bdef37d | ||
|
|
fd0519b955 | ||
|
|
d5ffb56fa8 | ||
|
|
d6cce9e2d8 | ||
|
|
08ec46637f | ||
|
|
f596795792 | ||
|
|
2c56bfd4aa | ||
|
|
755615da30 | ||
|
|
4cc0060c67 | ||
|
|
52af015c2f | ||
|
|
eae6d13284 | ||
|
|
2a20a7cf21 | ||
|
|
27251005ec | ||
|
|
90a9b2e5a1 | ||
|
|
cd1f12e9b4 | ||
|
|
734b9b6458 | ||
|
|
a120449c8d | ||
|
|
b005a1da56 | ||
|
|
afc75a09d9 | ||
|
|
c6e6c98875 | ||
|
|
fa1a5ab913 | ||
|
|
b671b590ad | ||
|
|
a46c98eed3 | ||
|
|
7ab5ed5bcb | ||
|
|
5e609edc31 | ||
|
|
ac466429f8 | ||
|
|
2804c50c8a | ||
|
|
c4315c25bc | ||
|
|
e2d5fa48cf | ||
|
|
c2375c897d | ||
|
|
49f181583b | ||
|
|
ea463a6548 | ||
|
|
1a567dbdc1 | ||
|
|
e4ec10ef49 | ||
|
|
3db4fc687a | ||
|
|
e136972cd7 | ||
|
|
7b1b32e16f | ||
|
|
1616f94b86 | ||
|
|
7043d1f3bf | ||
|
|
7bebfe1919 | ||
|
|
d33ded0426 | ||
|
|
66aa67581f | ||
|
|
3a95c05db9 | ||
|
|
79fc03bb0f | ||
|
|
67046e9d36 | ||
|
|
6f2965a5b0 | ||
|
|
197db449a1 | ||
|
|
f4800bb7f0 | ||
|
|
6a213561f6 | ||
|
|
8f8d31f0c9 | ||
|
|
f8cfdacc26 | ||
|
|
07223e27a4 | ||
|
|
4c6f46ad17 | ||
|
|
39dc288f9c | ||
|
|
467e88ef55 | ||
|
|
0965db5d57 | ||
|
|
c30339d9b8 | ||
|
|
369adcb173 | ||
|
|
d837b981d4 | ||
|
|
f455461c1e | ||
|
|
0087b188a1 | ||
|
|
98602f49d8 | ||
|
|
70466e5626 | ||
|
|
cc7f98a6fe | ||
|
|
fd13ad1fca | ||
|
|
5077f6fbca | ||
|
|
9b15ce6d29 | ||
|
|
5a8ca24566 | ||
|
|
d3f982cae1 | ||
|
|
28b58b9048 | ||
|
|
cb10dde97d | ||
|
|
2c179867e8 | ||
|
|
44fc6de82b | ||
|
|
f671af43cd | ||
|
|
8c0dc6422e | ||
|
|
c91e285475 | ||
|
|
642e41530e | ||
|
|
b09a3ccdae | ||
|
|
a1aab62cea | ||
|
|
e8b1aca45c | ||
|
|
3de98873d6 | ||
|
|
c030ede12c | ||
|
|
67f740c264 | ||
|
|
9b68337047 | ||
|
|
2bae971b92 | ||
|
|
c5c5fd859f | ||
|
|
15c46fd157 | ||
|
|
424950bd3e | ||
|
|
39a9968971 | ||
|
|
075a09f0df | ||
|
|
26e628e189 | ||
|
|
7489d9d186 | ||
|
|
7db5b6c7f3 | ||
|
|
9b7fa8cac2 | ||
|
|
5028051a34 | ||
|
|
83b3767df6 | ||
|
|
6182b96d16 | ||
|
|
a719382563 | ||
|
|
2aa6eb90e2 | ||
|
|
d2495455cd | ||
|
|
23d1dd23d1 | ||
|
|
573acd6a5d | ||
|
|
40ddbbfbb7 | ||
|
|
1b488b298b | ||
|
|
54b45943db | ||
|
|
89b3ae4c74 | ||
|
|
fe97a76d00 | ||
|
|
78094f69d7 | ||
|
|
4499d55464 | ||
|
|
4c12ca0607 | ||
|
|
1daa9575db | ||
|
|
f7c9961f8c | ||
|
|
e27a625069 | ||
|
|
42b1669cce | ||
|
|
74571afc9e | ||
|
|
f678f11c59 | ||
|
|
e685252b1d | ||
|
|
bb75fcb5a3 | ||
|
|
7653f36f31 | ||
|
|
0670148064 | ||
|
|
368b900a6e | ||
|
|
6378a78cbd | ||
|
|
d3bcdec0f2 | ||
|
|
82c074ce2e | ||
|
|
a313e2d386 | ||
|
|
578cb2f7e3 | ||
|
|
c8f56e1659 | ||
|
|
56631a3e6b | ||
|
|
84ee9de18c | ||
|
|
0aeb4e9173 | ||
|
|
bb65e5c373 | ||
|
|
45e2027c56 | ||
|
|
6dc5ef44b7 | ||
|
|
e88d3cca07 | ||
|
|
a4b7381141 | ||
|
|
2ea054dc72 | ||
|
|
13ec726ab2 | ||
|
|
2e32b65421 | ||
|
|
f48ca53cdb | ||
|
|
c5b632cb24 | ||
|
|
422e9ae99e | ||
|
|
68ada8fbe4 | ||
|
|
e40bf08691 | ||
|
|
a04c7222b2 | ||
|
|
172faec5a0 | ||
|
|
e01c3e7e4a | ||
|
|
7a3d5d0429 | ||
|
|
ddf1caaaaa | ||
|
|
021293cbb0 | ||
|
|
1438e61f1b | ||
|
|
182f4dbae7 | ||
|
|
26ec3fc7cf | ||
|
|
225c758795 | ||
|
|
5e653250f3 | ||
|
|
b7a3713946 | ||
|
|
44242fdb4a | ||
|
|
45515b2c06 | ||
|
|
2d95fe294c | ||
|
|
72cc44641b | ||
|
|
9ac3fa5269 | ||
|
|
d1ea141165 | ||
|
|
dca21fbb8c | ||
|
|
06812d5df8 | ||
|
|
564410d3cd | ||
|
|
4b1cec979a | ||
|
|
a5f1bc3895 | ||
|
|
b097d031b5 | ||
|
|
45df729711 | ||
|
|
802ee8a1ff | ||
|
|
e312970212 | ||
|
|
c0f4069a9b | ||
|
|
654352827e | ||
|
|
7dd210976d | ||
|
|
5302be8bc1 | ||
|
|
9ed7181e28 | ||
|
|
23ae4b01cb | ||
|
|
59764d3378 | ||
|
|
b8e2c729c1 | ||
|
|
530d80a011 | ||
|
|
2d306e8fda | ||
|
|
b3d9a70fd1 | ||
|
|
b880207f61 | ||
|
|
ee76d02999 | ||
|
|
804c33a375 | ||
|
|
de784714d9 | ||
|
|
2404e26b61 | ||
|
|
b191fdda2c | ||
|
|
e8adfd45d2 | ||
|
|
7158595801 | ||
|
|
ba97f63338 | ||
|
|
62eca2aedc | ||
|
|
b15f6b1fa7 | ||
|
|
5b22de589c | ||
|
|
d1f50f12af | ||
|
|
d40c1d9b31 | ||
|
|
b69041d4af | ||
|
|
64c5d9a09f | ||
|
|
dd170bb41a | ||
|
|
1e6e4033f8 | ||
|
|
01fabc8d9e | ||
|
|
2ca2539b53 | ||
|
|
51e2e2966f | ||
|
|
55d02d2db5 | ||
|
|
282a0b0c44 | ||
|
|
8432779b23 | ||
|
|
13b9982461 | ||
|
|
d76b8cebdc | ||
|
|
80315ae6d4 | ||
|
|
95e7344286 | ||
|
|
28f73727b5 | ||
|
|
68f5bf5759 | ||
|
|
075748d697 | ||
|
|
e8d86f94f2 | ||
|
|
d6bb802892 | ||
|
|
52680e9002 | ||
|
|
d6385d82ae | ||
|
|
d058de067c | ||
|
|
32d6d143dd | ||
|
|
b49432802b | ||
|
|
99d4d75d8e | ||
|
|
1f584d57ac | ||
|
|
2c1543aa82 | ||
|
|
4390cbbfae | ||
|
|
bbf9e5717e | ||
|
|
c7edc28bd9 | ||
|
|
6e0de6f7bf | ||
|
|
3659422165 | ||
|
|
1af30da44e | ||
|
|
56c082cb26 | ||
|
|
e8eca582de | ||
|
|
4084b352de | ||
|
|
633e4acf0e | ||
|
|
468df15d80 | ||
|
|
f4537b4fcb | ||
|
|
8bca345b45 | ||
|
|
ee80712c77 | ||
|
|
3cf7153f44 | ||
|
|
8e2fc75e48 | ||
|
|
4aa51c8583 | ||
|
|
3c6ebd7742 | ||
|
|
b85539dc17 | ||
|
|
4cae3f02ed | ||
|
|
469b436f10 | ||
|
|
66e3e6729b | ||
|
|
fc6a794714 | ||
|
|
d75ed3d5ac | ||
|
|
bd0a218214 | ||
|
|
f96129fa37 | ||
|
|
920418618a | ||
|
|
29247481a6 | ||
|
|
773710aeef | ||
|
|
d0c8ee57e6 | ||
|
|
cf2adc7f6a | ||
|
|
b621f24540 | ||
|
|
99be638525 | ||
|
|
d35c204e07 | ||
|
|
d8b4267668 | ||
|
|
3c2f3be451 | ||
|
|
e846cf8672 | ||
|
|
8804bce6c0 | ||
|
|
83acda6d05 | ||
|
|
063719532f | ||
|
|
7b1b061355 | ||
|
|
ed4540887e | ||
|
|
6968476ed0 | ||
|
|
5d2c7c3058 | ||
|
|
e6cb90e545 | ||
|
|
ec73f4dbea | ||
|
|
4f41a52ee7 | ||
|
|
c097956fcb | ||
|
|
8cbc17ed98 | ||
|
|
50d89d0f13 | ||
|
|
7b4d13b73e | ||
|
|
2909aa1656 | ||
|
|
64131b6764 | ||
|
|
0b78d75a21 | ||
|
|
b35c342960 | ||
|
|
24c858d379 | ||
|
|
b8a31a8be9 | ||
|
|
02c30d6454 | ||
|
|
985f003e6d | ||
|
|
98045e1e2e | ||
|
|
5762ce58a4 | ||
|
|
2787ee71fc | ||
|
|
e61a6df4d7 | ||
|
|
0a5c2ecbf5 | ||
|
|
6bfab696ad | ||
|
|
928f2dd496 | ||
|
|
bcf75892f7 | ||
|
|
594761385f | ||
|
|
d05fba06ec | ||
|
|
25155b1b38 | ||
|
|
ded6c9c199 | ||
|
|
c62e6c0045 | ||
|
|
b3feda9fd1 | ||
|
|
7ef8e2c8b8 | ||
|
|
d5ff211871 | ||
|
|
51c23e3842 | ||
|
|
557b30815e | ||
|
|
145459bfc3 | ||
|
|
f97a6a2bee | ||
|
|
1134422891 | ||
|
|
6012275c7b | ||
|
|
2f07063375 | ||
|
|
310d1ed485 | ||
|
|
a48edbb16e | ||
|
|
d6258409fc | ||
|
|
1b94b1247b | ||
|
|
9ef63ae60e | ||
|
|
f99de4be48 | ||
|
|
80fd1820c2 | ||
|
|
0a4a983d20 | ||
|
|
c2cc830691 | ||
|
|
4d0490d1c6 | ||
|
|
02d1838547 | ||
|
|
5c6edff778 | ||
|
|
7cbca32ddd | ||
|
|
8950e315f8 | ||
|
|
a703df40c0 | ||
|
|
225fce8810 | ||
|
|
bdc0f0fcdd | ||
|
|
5bbb8c4858 | ||
|
|
35b9551bfb | ||
|
|
c8c5a05b39 | ||
|
|
2771f0301a | ||
|
|
c4f04edc59 | ||
|
|
5530422846 | ||
|
|
ce77c81fb5 | ||
|
|
bc488d4ac2 | ||
|
|
fe72a10346 | ||
|
|
0da88398b4 | ||
|
|
c42de3da20 | ||
|
|
4bf9f36baa | ||
|
|
380cb192c7 | ||
|
|
8882e19ec5 | ||
|
|
7f52059b98 | ||
|
|
1ce3cc4d7c | ||
|
|
657c71e94d | ||
|
|
c8cfb3c7b7 | ||
|
|
6e7e90acf4 | ||
|
|
6d3a556d34 | ||
|
|
7d9188b659 | ||
|
|
4f0a805c79 | ||
|
|
53f3320492 | ||
|
|
f9ce51b4a5 | ||
|
|
f8bf432c0d | ||
|
|
a822482172 | ||
|
|
b22f8a0b02 | ||
|
|
227331bf24 | ||
|
|
744688cbe1 | ||
|
|
79c8126c4a | ||
|
|
0b7c8e4ef7 | ||
|
|
35e00f61d8 | ||
|
|
aba3d58df8 | ||
|
|
45984a8166 | ||
|
|
ea5655ae42 | ||
|
|
f06cb0dfbb | ||
|
|
1abae55976 | ||
|
|
a83ed40ec4 | ||
|
|
75ecae4672 | ||
|
|
16a6f942c5 | ||
|
|
aa98508e57 | ||
|
|
583383aee1 | ||
|
|
13f69f81d7 | ||
|
|
43c34fcd64 | ||
|
|
c272238539 | ||
|
|
6e0d2706a8 | ||
|
|
25a7289ce8 | ||
|
|
1ba7d045e4 | ||
|
|
a4d75befe7 | ||
|
|
88377529bc | ||
|
|
91b9a0280f | ||
|
|
25173ae85c | ||
|
|
ad3350705e |
@@ -1,2 +0,0 @@
|
|||||||
[config]
|
|
||||||
project = Oqtane.Server/Oqtane.Server.csproj
|
|
||||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Oqtane.Server/wwwroot/Modules/Templates/External/Package/*.sh eol=lf
|
||||||
|
Oqtane.Server/wwwroot/Themes/Templates/External/Package/*.sh eol=lf
|
||||||
25
.gitea/workflows/build-container.yml
Normal file
25
.gitea/workflows/build-container.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
name: build-docker-imge
|
||||||
|
on:
|
||||||
|
- push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build the docker container
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: "Git clone"
|
||||||
|
run: git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git .
|
||||||
|
- name: "Git checkout"
|
||||||
|
run: git checkout "${{ gitea.sha }}"
|
||||||
|
- uses: aevea/action-kaniko@master
|
||||||
|
name: Run Kaniko to build our api docker container.
|
||||||
|
with:
|
||||||
|
image: kocoded/oqtane.framework
|
||||||
|
tag: ${{ git.workflow_sha }}
|
||||||
|
tag_with_latest: github.ref == 'refs/heads/master'
|
||||||
|
registry: git.kocoder.xyz
|
||||||
|
username: ${{ secrets.CI_RUNNER_USER }}
|
||||||
|
password: ${{ secrets.CI_RUNNER_TOKEN }}
|
||||||
|
build_file: Dockerfile
|
||||||
|
target: deploy
|
||||||
|
|
||||||
26
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: Bug Report
|
||||||
|
about: Create a bug report to help us improve the product
|
||||||
|
title: "[BUG] "
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Oqtane Info
|
||||||
|
|
||||||
|
Version - #.#.#
|
||||||
|
Render Mode - Static
|
||||||
|
Interactivity - Server
|
||||||
|
Database - SQL Server
|
||||||
|
|
||||||
|
### Describe the bug
|
||||||
|
|
||||||
|
|
||||||
|
### Expected Behavior
|
||||||
|
|
||||||
|
|
||||||
|
### Steps To Reproduce
|
||||||
|
|
||||||
|
|
||||||
|
### Anything else?
|
||||||
20
.github/ISSUE_TEMPLATE/enhancement-request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/enhancement-request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Enhancement Request
|
||||||
|
about: 'Suggest a product enhancement '
|
||||||
|
title: "[ENH] "
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Oqtane Info
|
||||||
|
|
||||||
|
Version - #.#.#
|
||||||
|
Render Mode - Static
|
||||||
|
Interactivity - Server
|
||||||
|
Database - SQL Server
|
||||||
|
|
||||||
|
### Describe the enhancement
|
||||||
|
|
||||||
|
|
||||||
|
### Anything else?
|
||||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -10,20 +10,29 @@ msbuild.binlog
|
|||||||
*.zip
|
*.zip
|
||||||
|
|
||||||
*.idea
|
*.idea
|
||||||
|
_ReSharper.Caches
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
Oqtane.Server/appsettings.json
|
Oqtane.Server/appsettings.json
|
||||||
Oqtane.Server/Data
|
Oqtane.Server/Data
|
||||||
|
|
||||||
/Oqtane.Server/Properties/PublishProfiles/FolderProfile.pubxml
|
Oqtane.Server/Properties/PublishProfiles/FolderProfile.pubxml
|
||||||
Oqtane.Server/Content
|
Oqtane.Server/Content
|
||||||
Oqtane.Server/Packages
|
Oqtane.Server/Packages
|
||||||
Oqtane.Server/wwwroot/Content
|
Oqtane.Server/wwwroot/Content
|
||||||
Oqtane.Server/wwwroot/Packages/*.log
|
Oqtane.Server/wwwroot/Packages/*.log
|
||||||
|
|
||||||
Oqtane.Server/wwwroot/Modules
|
Oqtane.Server/wwwroot/_content/*
|
||||||
|
!Oqtane.Server/wwwroot/_content/Placeholder.txt
|
||||||
|
|
||||||
|
Oqtane.Server/wwwroot/Modules/*
|
||||||
!Oqtane.Server/wwwroot/Modules/Oqtane.Modules.*
|
!Oqtane.Server/wwwroot/Modules/Oqtane.Modules.*
|
||||||
!Oqtane.Server/wwwroot/Modules/Templates
|
!Oqtane.Server/wwwroot/Modules/Templates
|
||||||
|
Oqtane.Server/wwwroot/Modules/Templates/*
|
||||||
|
!Oqtane.Server/wwwroot/Modules/Templates/External
|
||||||
|
|
||||||
Oqtane.Server/wwwroot/Themes
|
Oqtane.Server/wwwroot/Themes/*
|
||||||
!Oqtane.Server/wwwroot/Themes/Oqtane.Themes.*
|
!Oqtane.Server/wwwroot/Themes/Oqtane.Themes.*
|
||||||
!Oqtane.Server/wwwroot/Themes/Templates
|
!Oqtane.Server/wwwroot/Themes/Templates
|
||||||
|
Oqtane.Server/wwwroot/Themes/Templates/*
|
||||||
|
Oqtane.Server/wwwroot/Themes/Templates/External
|
||||||
|
|||||||
24
CONTRIBUTING.md
Normal file
24
CONTRIBUTING.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Contributing to Oqtane
|
||||||
|
|
||||||
|
## How to Contribute
|
||||||
|
|
||||||
|
We track all of our issues on Github. If you want to contribute, everything starts with an issue. If you don't have an issue yet, you can add one. Then a core contributor will tag it as either an enhancement [ENH] or a bug [BUG]. Tagged issues are open for contribution.
|
||||||
|
|
||||||
|
## Use GitHub-flow process
|
||||||
|
- Make a comment on the issue that you intend to work on it and read all the comments to gain a full understanding.
|
||||||
|
- Fork the repository
|
||||||
|
- Create a new branch and update your comment on the issue with a llink to the branch
|
||||||
|
- Make your changes and commit them
|
||||||
|
- Push to the branch
|
||||||
|
- Create a pull request
|
||||||
|
|
||||||
|
## Reporting Bugs
|
||||||
|
|
||||||
|
- Check if the issue has already been reported.
|
||||||
|
- Open a new issue if it hasn’t been reported.
|
||||||
|
|
||||||
|
## Requesting Features
|
||||||
|
|
||||||
|
- Use the feature request template in the Issues tab.
|
||||||
|
|
||||||
|
Thank you for contributing!
|
||||||
24
Dockerfile
Normal file
24
Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Build
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||||
|
|
||||||
|
WORKDIR /source
|
||||||
|
|
||||||
|
COPY --link . .
|
||||||
|
|
||||||
|
RUN dotnet restore /source/Oqtane.sln
|
||||||
|
|
||||||
|
RUN dotnet build "/source/Oqtane.sln" -c Release -o /source/build/
|
||||||
|
|
||||||
|
# Publish
|
||||||
|
FROM build AS publish
|
||||||
|
|
||||||
|
RUN dotnet publish "Oqtane.Server/Oqtane.Server.csproj" -c Release -o /source/publish/
|
||||||
|
|
||||||
|
# Deploy
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS deploy
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
||||||
|
COPY --from=publish /source/publish/ /app/
|
||||||
|
ENTRYPOINT ["dotnet", "Oqtane.Server.dll"]
|
||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2018-2022 .NET Foundation
|
Copyright (c) 2018-2025 .NET Foundation
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
@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; }
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
_installation = await InstallationService.IsInstalled();
|
|
||||||
if (_installation.Alias != null)
|
|
||||||
{
|
|
||||||
SiteState.Alias = _installation.Alias;
|
|
||||||
}
|
|
||||||
_initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnAfterRender(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
_display = "";
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ChangeState(PageState pageState)
|
|
||||||
{
|
|
||||||
PageState = pageState;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.Localization
|
namespace Microsoft.Extensions.Localization
|
||||||
{
|
{
|
||||||
public static class OqtaneLocalizationExtensions
|
public static class OqtaneLocalizationExtensions
|
||||||
@@ -18,5 +20,42 @@ namespace Microsoft.Extensions.Localization
|
|||||||
}
|
}
|
||||||
return localizedValue;
|
return localizedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an IStringLocalizer based on a type name. This extension method is useful in scenarios where the default IStringLocalizer is unable to locate the resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="localizerFactory"></param>
|
||||||
|
/// <param name="fullTypeName">the full type name ie. GetType().FullName</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IStringLocalizer Create(this IStringLocalizerFactory localizerFactory, string fullTypeName)
|
||||||
|
{
|
||||||
|
var typename = fullTypeName;
|
||||||
|
|
||||||
|
// handle generic types
|
||||||
|
var type = Type.GetType(fullTypeName);
|
||||||
|
if (type.IsGenericType)
|
||||||
|
{
|
||||||
|
typename = type.GetGenericTypeDefinition().FullName;
|
||||||
|
typename = typename.Substring(0, typename.IndexOf("`")); // remove generic type info
|
||||||
|
}
|
||||||
|
|
||||||
|
// format typename
|
||||||
|
if (typename.Contains(","))
|
||||||
|
{
|
||||||
|
typename = typename.Substring(0, typename.IndexOf(",")); // remove assembly info
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove rootnamespace
|
||||||
|
var rootnamespace = "";
|
||||||
|
var attributes = type.Assembly.GetCustomAttributes(typeof(RootNamespaceAttribute), false);
|
||||||
|
if (attributes.Length > 0)
|
||||||
|
{
|
||||||
|
rootnamespace = ((RootNamespaceAttribute)attributes[0]).RootNamespace;
|
||||||
|
}
|
||||||
|
typename = typename.Replace(rootnamespace + ".", "");
|
||||||
|
|
||||||
|
// create IStringLocalizer using factory
|
||||||
|
return localizerFactory.Create(typename, type.Assembly.GetName().Name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
using Oqtane.Interfaces;
|
||||||
using Oqtane.Providers;
|
using Oqtane.Providers;
|
||||||
using Oqtane.Services;
|
using Oqtane.Services;
|
||||||
using Oqtane.Shared;
|
using Oqtane.Shared;
|
||||||
@@ -7,16 +8,17 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
{
|
{
|
||||||
public static class OqtaneServiceCollectionExtensions
|
public static class OqtaneServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddOqtaneAuthorization(this IServiceCollection services)
|
public static IServiceCollection AddOqtaneAuthentication(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddAuthorizationCore();
|
services.AddAuthorizationCore();
|
||||||
|
services.AddCascadingAuthenticationState();
|
||||||
services.AddScoped<IdentityAuthenticationStateProvider>();
|
services.AddScoped<IdentityAuthenticationStateProvider>();
|
||||||
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
|
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IServiceCollection AddOqtaneScopedServices(this IServiceCollection services)
|
public static IServiceCollection AddOqtaneClientScopedServices(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddScoped<SiteState>();
|
services.AddScoped<SiteState>();
|
||||||
services.AddScoped<IInstallationService, InstallationService>();
|
services.AddScoped<IInstallationService, InstallationService>();
|
||||||
@@ -49,6 +51,14 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddScoped<IUrlMappingService, UrlMappingService>();
|
services.AddScoped<IUrlMappingService, UrlMappingService>();
|
||||||
services.AddScoped<IVisitorService, VisitorService>();
|
services.AddScoped<IVisitorService, VisitorService>();
|
||||||
services.AddScoped<ISyncService, SyncService>();
|
services.AddScoped<ISyncService, SyncService>();
|
||||||
|
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
|
||||||
|
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
||||||
|
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
||||||
|
services.AddScoped<ITimeZoneService, TimeZoneService>();
|
||||||
|
|
||||||
|
// providers
|
||||||
|
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
||||||
|
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.TextAreaTextEditor>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
14
Oqtane.Client/IconResources.cs
Normal file
14
Oqtane.Client/IconResources.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Oqtane
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Dummy class used to collect shared resource strings for this application
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This class is mostly used with IStringLocalizer and IHtmlLocalizer interfaces.
|
||||||
|
/// The class must reside at the project root.
|
||||||
|
/// </remarks>
|
||||||
|
public class IconResources
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
119
Oqtane.Client/Installer/Controls/AzureSqlConfig.razor
Normal file
119
Oqtane.Client/Installer/Controls/AzureSqlConfig.razor
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
@namespace Oqtane.Installer.Controls
|
||||||
|
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
||||||
|
@inject IStringLocalizer<SqlServerConfig> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="server" HelpText="Enter the database server name. This might include a port number as well if you are using a cloud service." ResourceKey="Server">Server:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="server" type="text" class="form-control" @bind="@_server" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="database" HelpText="Enter the name of the database" ResourceKey="Database">Database:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="database" type="text" class="form-control" @bind="@_database" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="security" HelpText="Select your security method" ResourceKey="Security">Security:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="security" class="form-select custom-select" @bind="@_security">
|
||||||
|
<option value="integrated" selected>@Localizer["Integrated"]</option>
|
||||||
|
<option value="custom">@Localizer["Custom"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (_security == "custom")
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="uid" HelpText="Enter the username to use for the database" ResourceKey="Uid">User Id:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="uid" type="text" class="form-control" @bind="@_uid" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="pwd" type="@_passwordType" class="form-control" @bind="@_pwd" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="encryption" HelpText="Specify if you are using an encrypted database connection. It is highly recommended to use encryption in a production environment." ResourceKey="Encryption">Encryption:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="encryption" class="form-select custom-select" @bind="@_encryption">
|
||||||
|
<option value="true">@SharedLocalizer["True"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["False"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (_encryption == "true")
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="trustservercertificate" HelpText="Specify the type of certificate you are using for encryption. Verifiable is equivalent to False. Self Signed is equivalent to True." ResourceKey="TrustServerCertificate">Certificate:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="encryption" class="form-select custom-select" @bind="@_trustservercertificate">
|
||||||
|
<option value="true">@Localizer["Self Signed"]</option>
|
||||||
|
<option value="false">@Localizer["Verifiable"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string _server = "tcp:{SQL Server Name}.database.windows.net,1433";
|
||||||
|
private string _database = "{SQL Database Name}";
|
||||||
|
private string _security = "custom";
|
||||||
|
private string _uid = "{SQL Administrator Login}";
|
||||||
|
private string _pwd = String.Empty;
|
||||||
|
private string _passwordType = "password";
|
||||||
|
private string _togglePassword = string.Empty;
|
||||||
|
private string _encryption = "true";
|
||||||
|
private string _trustservercertificate = "false";
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetConnectionString()
|
||||||
|
{
|
||||||
|
var connectionString = String.Empty;
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database))
|
||||||
|
{
|
||||||
|
connectionString = $"Data Source={_server};Initial Catalog={_database};";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_security == "integrated")
|
||||||
|
{
|
||||||
|
connectionString += "Integrated Security=SSPI;";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
connectionString += $"User ID={_uid};Password={_pwd};";
|
||||||
|
}
|
||||||
|
connectionString += $"Encrypt={_encryption};";
|
||||||
|
connectionString += $"TrustServerCertificate={_trustservercertificate};";
|
||||||
|
|
||||||
|
return connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordType == "password")
|
||||||
|
{
|
||||||
|
_passwordType = "text";
|
||||||
|
_togglePassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordType = "password";
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
@namespace Oqtane.Installer.Controls
|
@namespace Oqtane.Installer.Controls
|
||||||
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
|
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
|
||||||
@@ -28,7 +29,10 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="pwd" type="password" class="form-control" @bind="@_pwd" autocomplete="new-password" />
|
<div class="input-group">
|
||||||
|
<input id="pwd" type="@_passwordType" class="form-control" @bind="@_pwd" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -38,6 +42,13 @@
|
|||||||
private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
|
private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
|
||||||
private string _uid = String.Empty;
|
private string _uid = String.Empty;
|
||||||
private string _pwd = String.Empty;
|
private string _pwd = String.Empty;
|
||||||
|
private string _passwordType = "password";
|
||||||
|
private string _togglePassword = string.Empty;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
|
||||||
public string GetConnectionString()
|
public string GetConnectionString()
|
||||||
{
|
{
|
||||||
@@ -55,4 +66,18 @@
|
|||||||
|
|
||||||
return connectionString;
|
return connectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordType == "password")
|
||||||
|
{
|
||||||
|
_passwordType = "text";
|
||||||
|
_togglePassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordType = "password";
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
@namespace Oqtane.Installer.Controls
|
@namespace Oqtane.Installer.Controls
|
||||||
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
||||||
@inject IStringLocalizer<PostgreSQLConfig> Localizer
|
@inject IStringLocalizer<PostgreSQLConfig> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
|
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
|
||||||
@@ -40,7 +41,10 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="pwd" type="password" class="form-control" @bind="@_pwd" autocomplete="new-password" />
|
<div class="input-group">
|
||||||
|
<input id="pwd" type="@_passwordType" class="form-control" @bind="@_pwd" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -52,6 +56,13 @@
|
|||||||
private string _security = "integrated";
|
private string _security = "integrated";
|
||||||
private string _uid = String.Empty;
|
private string _uid = String.Empty;
|
||||||
private string _pwd = String.Empty;
|
private string _pwd = String.Empty;
|
||||||
|
private string _passwordType = "password";
|
||||||
|
private string _togglePassword = string.Empty;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
|
||||||
public string GetConnectionString()
|
public string GetConnectionString()
|
||||||
{
|
{
|
||||||
@@ -80,4 +91,18 @@
|
|||||||
|
|
||||||
return connectionString;
|
return connectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordType == "password")
|
||||||
|
{
|
||||||
|
_passwordType = "text";
|
||||||
|
_togglePassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordType = "password";
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
|
<Label Class="col-sm-3" For="server" HelpText="Enter the database server name. This might include a port number as well if you are using a cloud service." ResourceKey="Server">Server:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="server" type="text" class="form-control" @bind="@_server" />
|
<input id="server" type="text" class="form-control" @bind="@_server" />
|
||||||
</div>
|
</div>
|
||||||
@@ -35,7 +35,10 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="pwd" type="password" class="form-control" @bind="@_pwd" autocomplete="new-password" />
|
<div class="input-group">
|
||||||
|
<input id="pwd" type="@_passwordType" class="form-control" @bind="@_pwd" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -51,7 +54,7 @@
|
|||||||
@if (_encryption == "true")
|
@if (_encryption == "true")
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="trustservercertificate" HelpText="Specify the type of certificate you are using for encryption" ResourceKey="TrustServerCertificate">Certificate:</Label>
|
<Label Class="col-sm-3" For="trustservercertificate" HelpText="Specify the type of certificate you are using for encryption. Verifiable is equivalent to False. Self Signed is equivalent to True." ResourceKey="TrustServerCertificate">Certificate:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="encryption" class="form-select custom-select" @bind="@_trustservercertificate">
|
<select id="encryption" class="form-select custom-select" @bind="@_trustservercertificate">
|
||||||
<option value="true">@Localizer["Self Signed"]</option>
|
<option value="true">@Localizer["Self Signed"]</option>
|
||||||
@@ -67,9 +70,16 @@
|
|||||||
private string _security = "integrated";
|
private string _security = "integrated";
|
||||||
private string _uid = String.Empty;
|
private string _uid = String.Empty;
|
||||||
private string _pwd = String.Empty;
|
private string _pwd = String.Empty;
|
||||||
|
private string _passwordType = "password";
|
||||||
|
private string _togglePassword = string.Empty;
|
||||||
private string _encryption = "false";
|
private string _encryption = "false";
|
||||||
private string _trustservercertificate = "false";
|
private string _trustservercertificate = "false";
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
|
||||||
public string GetConnectionString()
|
public string GetConnectionString()
|
||||||
{
|
{
|
||||||
var connectionString = String.Empty;
|
var connectionString = String.Empty;
|
||||||
@@ -92,4 +102,18 @@
|
|||||||
|
|
||||||
return connectionString;
|
return connectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordType == "password")
|
||||||
|
{
|
||||||
|
_passwordType = "text";
|
||||||
|
_togglePassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordType = "password";
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,15 +5,17 @@
|
|||||||
@inject ISiteService SiteService
|
@inject ISiteService SiteService
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
@inject IDatabaseService DatabaseService
|
@inject IDatabaseService DatabaseService
|
||||||
|
@inject ISiteTemplateService SiteTemplateService
|
||||||
@inject IJSRuntime JSRuntime
|
@inject IJSRuntime JSRuntime
|
||||||
@inject IStringLocalizer<Installer> Localizer
|
@inject IStringLocalizer<Installer> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
@inject SiteState SiteState
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="mx-auto text-center">
|
<div class="mx-auto text-center">
|
||||||
<img src="oqtane-black.png" />
|
<img src="oqtane-black.png" />
|
||||||
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version</div>
|
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET @Environment.Version.Major)</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr class="app-rule" />
|
<hr class="app-rule" />
|
||||||
@@ -26,45 +28,58 @@
|
|||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@if (_databases != null)
|
@if (_databases != null)
|
||||||
{
|
{
|
||||||
<select id="databasetype" class="form-select custom-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))">
|
<div class="input-group">
|
||||||
@foreach (var database in _databases)
|
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
|
||||||
{
|
@foreach (var database in _databases)
|
||||||
if (database.IsDefault)
|
{
|
||||||
{
|
<option value="@database.Name">@Localizer[@database.Name]</option>
|
||||||
<option value="@database.Name" selected>@Localizer[@database.Name]</option>
|
}
|
||||||
}
|
</select>
|
||||||
else
|
@if (!_showConnectionString)
|
||||||
{
|
{
|
||||||
<option value="@database.Name">@Localizer[@database.Name]</option>
|
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
</select>
|
{
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@{
|
@if (!_showConnectionString)
|
||||||
if (_databaseConfigType != null)
|
{
|
||||||
{
|
if (_databaseConfigType != null)
|
||||||
@DatabaseConfigComponent;
|
{
|
||||||
}
|
@DatabaseConfigComponent
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">Settings:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<h2>@Localizer["ApplicationAdmin"]</h2><br />
|
<h2>@Localizer["ApplicationAdmin"]</h2><br />
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="username" HelpText="Provide a username for the primary user accountt" ResourceKey="Username">Username:</Label>
|
<Label Class="col-sm-3" For="username" HelpText="Provide a username for the primary user account" ResourceKey="Username">Username:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="username" type="text" class="form-control" @bind="@_hostUsername" />
|
<input id="username" type="text" class="form-control" maxlength="256" @bind="@_hostUsername" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
|
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_hostPassword" autocomplete="new-password" />
|
<input id="password" type="@_passwordType" class="form-control" maxlength="256" @bind="@_hostPassword" autocomplete="new-password" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,15 +87,35 @@
|
|||||||
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
|
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="confirm" type="@_passwordtype" 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="@TogglePassword">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword" tabindex="-1">@_toggleConfirmPassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
|
<Label Class="col-sm-3" For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input type="text" class="form-control" @bind="@_hostEmail" />
|
<input type="text" class="form-control" maxlength="256" @bind="@_hostEmail" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="name" HelpText="Provide the full name of the host user" ResourceKey="Name">Full Name:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" maxlength="50" @bind="@_hostName" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="template" HelpText="Select a site template" ResourceKey="Template">Template:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
@if (_templates != null)
|
||||||
|
{
|
||||||
|
<select id="template" class="form-select" @bind="@_template" required>
|
||||||
|
@foreach (var template in _templates)
|
||||||
|
{
|
||||||
|
<option value="@template.TypeName">@template.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,7 +125,13 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="mx-auto text-center">
|
<div class="mx-auto text-center">
|
||||||
<button type="button" class="btn btn-success" @onclick="Install">@Localizer["InstallNow"]</button><br /><br />
|
<button type="button" class="btn btn-success" @onclick="Install">@Localizer["InstallNow"]</button><br /><br />
|
||||||
<ModuleMessage Message="@_message" Type="MessageType.Error"></ModuleMessage>
|
@if (!string.IsNullOrEmpty(_message))
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show mb-3" role="alert">
|
||||||
|
@((MarkupString)_message)
|
||||||
|
<button type="button" class="btn-close" aria-label="Close" @onclick="DismissModal"></button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="app-progress-indicator" style="@_loadingDisplay"></div>
|
<div class="app-progress-indicator" style="@_loadingDisplay"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -102,143 +143,198 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Database> _databases;
|
private List<Database> _databases;
|
||||||
private string _databaseName;
|
private string _databaseName;
|
||||||
private Type _databaseConfigType;
|
private Type _databaseConfigType;
|
||||||
private object _databaseConfig;
|
private object _databaseConfig;
|
||||||
private RenderFragment DatabaseConfigComponent { get; set; }
|
private RenderFragment DatabaseConfigComponent { get; set; }
|
||||||
|
private bool _showConnectionString = false;
|
||||||
|
private string _connectionString = string.Empty;
|
||||||
|
|
||||||
private string _hostUsername = string.Empty;
|
private string _hostUsername = string.Empty;
|
||||||
private string _hostPassword = string.Empty;
|
private string _hostPassword = string.Empty;
|
||||||
private string _passwordtype = "password";
|
private string _passwordType = "password";
|
||||||
private string _togglepassword = string.Empty;
|
private string _confirmPasswordType = "password";
|
||||||
private string _confirmPassword = string.Empty;
|
private string _togglePassword = string.Empty;
|
||||||
private string _hostEmail = string.Empty;
|
private string _toggleConfirmPassword = string.Empty;
|
||||||
private bool _register = true;
|
private string _confirmPassword = string.Empty;
|
||||||
private string _message = string.Empty;
|
private string _hostEmail = string.Empty;
|
||||||
private string _loadingDisplay = "display: none;";
|
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;";
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
// include CSS
|
||||||
_databases = await DatabaseService.GetDatabasesAsync();
|
var content = $"<link rel=\"stylesheet\" href=\"{Constants.BootstrapStylesheetUrl}\" integrity=\"{Constants.BootstrapStylesheetIntegrity}\" crossorigin=\"anonymous\" type=\"text/css\"/>";
|
||||||
if (_databases.Exists(item => item.IsDefault))
|
SiteState.AppendHeadContent(content);
|
||||||
{
|
|
||||||
_databaseName = _databases.Find(item => item.IsDefault).Name;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_databaseName = "LocalDB";
|
|
||||||
}
|
|
||||||
LoadDatabaseConfigComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DatabaseChanged(ChangeEventArgs eventArgs)
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
{
|
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
|
||||||
try
|
|
||||||
{
|
|
||||||
_databaseName = (string)eventArgs.Value;
|
|
||||||
|
|
||||||
LoadDatabaseConfigComponent();
|
_databases = await DatabaseService.GetDatabasesAsync();
|
||||||
}
|
if (_databases.Exists(item => item.IsDefault))
|
||||||
catch
|
{
|
||||||
{
|
_databaseName = _databases.Find(item => item.IsDefault).Name;
|
||||||
_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();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
var interop = new Interop(JSRuntime);
|
|
||||||
await interop.IncludeLink("", "stylesheet", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/css/bootstrap.min.css", "text/css", "sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w==", "anonymous", "");
|
|
||||||
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.3/js/bootstrap.bundle.min.js", "sha512-pax4MlgXjHEPfCwcJLQhigY7+N8rt6bVvWLFyUMuxShv170X53TRzGPmPkZmGBhk+jikR8WBM4yl7A9WMHHqvg==", "anonymous", "", "head");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Install()
|
|
||||||
{
|
|
||||||
var connectionString = String.Empty;
|
|
||||||
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();
|
|
||||||
|
|
||||||
Uri uri = new Uri(NavigationManager.Uri);
|
|
||||||
|
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
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"];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_message = Localizer["Message.Require.DbInfo"];
|
_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 TogglePassword()
|
private void LoadDatabaseConfigComponent()
|
||||||
{
|
{
|
||||||
if (_passwordtype == "password")
|
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||||
{
|
if (database != null)
|
||||||
_passwordtype = "text";
|
{
|
||||||
_togglepassword = SharedLocalizer["HidePassword"];
|
_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)
|
||||||
|
{
|
||||||
|
// include JavaScript
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 = _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 = string.Join("<br />", result.Errors.Select(i => !string.IsNullOrEmpty(i.Value) ? i.Value : Localizer[i.Key]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_passwordtype = "password";
|
_message = Localizer["Message.Require.DbInfo"];
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordType == "password")
|
||||||
|
{
|
||||||
|
_passwordType = "text";
|
||||||
|
_togglePassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordType = "password";
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleConfirmPassword()
|
||||||
|
{
|
||||||
|
if (_confirmPasswordType == "password")
|
||||||
|
{
|
||||||
|
_confirmPasswordType = "text";
|
||||||
|
_toggleConfirmPassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_confirmPasswordType = "password";
|
||||||
|
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleConnectionString()
|
||||||
|
{
|
||||||
|
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||||
|
{
|
||||||
|
_connectionString = databaseConfigControl.GetConnectionString();
|
||||||
|
}
|
||||||
|
_showConnectionString = !_showConnectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DismissModal()
|
||||||
|
{
|
||||||
|
_message = "";
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,29 +4,37 @@
|
|||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<div class="row">
|
@if (_pages != null)
|
||||||
@foreach (var p in _pages)
|
{
|
||||||
{
|
<div class="row">
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
|
@foreach (var p in _pages)
|
||||||
{
|
{
|
||||||
string url = NavigateUrl(p.Path);
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||||
<div class="col-md-2 mx-auto text-center">
|
{
|
||||||
<NavLink class="nav-link" href="@url" Match="NavLinkMatch.All">
|
string url = NavigateUrl(p.Path);
|
||||||
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@SharedLocalizer[p.Name]
|
<div class="col-md-2 mx-auto text-center my-3">
|
||||||
</NavLink>
|
<NavLink class="nav-link text-body" href="@url" Match="NavLinkMatch.All">
|
||||||
</div>
|
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>
|
||||||
|
<div class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</div>
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
</div>
|
||||||
</div>
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Page> _pages;
|
private List<Page> _pages;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
|
public override string RenderMode => RenderModes.Static;
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
|
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
|
||||||
_pages = PageState.Pages.Where(item => item.ParentId == admin?.PageId).ToList();
|
if (admin != null)
|
||||||
|
{
|
||||||
|
_pages = PageState.Pages.Where(item => item.ParentId == admin.PageId).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,7 @@
|
|||||||
Module module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
|
Module module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
string message = string.Format(Localizer["Error.Module.Load"], module.ModuleDefinitionName);
|
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], module.ModuleDefinitionName), MessageType.Error);
|
||||||
AddModuleMessage(message, MessageType.Error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await logger.LogCritical("Error Loading Module {Module}", module);
|
await logger.LogCritical("Error Loading Module {Module}", module);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Add> Localizer
|
@inject IStringLocalizer<Add> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
|
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
if (_url == string.Empty || _folderId == -1)
|
if (_url == string.Empty || _folderId == -1)
|
||||||
@@ -93,7 +95,7 @@
|
|||||||
_name = _url.Substring(_url.LastIndexOf("/", StringComparison.Ordinal) + 1);
|
_name = _url.Substring(_url.LastIndexOf("/", StringComparison.Ordinal) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Constants.UploadableFiles.Split(',').Contains(Path.GetExtension(_name).ToLower().Replace(".", "")))
|
if (!PageState.Site.UploadableFiles.Split(',').Contains(Path.GetExtension(_name).ToLower().Replace(".", "")))
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Download.InvalidExtension"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Download.InvalidExtension"], MessageType.Warning);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -40,10 +40,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveFile">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveFile">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
<br />
|
@if (_name.ToLower().EndsWith(".zip"))
|
||||||
<br />
|
{
|
||||||
|
<button type="button" class="btn btn-primary mx-1" @onclick="UnzipFile">Unzip</button>
|
||||||
|
}
|
||||||
|
<br /><br />
|
||||||
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
@@ -126,4 +130,18 @@
|
|||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task UnzipFile()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await FileService.UnzipFileAsync(_fileId);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Unzipping File {FileId} {Error}", _fileId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.File.Unzip"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,67 +8,95 @@
|
|||||||
|
|
||||||
@if (_folders != null)
|
@if (_folders != null)
|
||||||
{
|
{
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<TabStrip>
|
||||||
<div class="container">
|
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
|
||||||
<div class="row mb-1 align-items-center">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<Label Class="col-sm-3" For="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
|
<div class="container">
|
||||||
<div class="col-sm-9">
|
<div class="row mb-1 align-items-center">
|
||||||
<select id="parent" class="form-select" @bind="@_parentId" required>
|
<Label Class="col-sm-3" For="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
|
||||||
@if (PageState.QueryString.ContainsKey("id"))
|
<div class="col-sm-9">
|
||||||
{
|
@if (_parentId == -1)
|
||||||
<option value="-1"><@Localizer["NoParent"]></option>
|
{
|
||||||
}
|
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||||
@foreach (Folder folder in _folders)
|
<option value="-1"><@Localizer["NoParent"]></option>
|
||||||
{
|
</select>
|
||||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
}
|
||||||
}
|
else
|
||||||
</select>
|
{
|
||||||
|
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||||
|
@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">
|
||||||
|
@if (_isSystem)
|
||||||
|
{
|
||||||
|
<input id="name" class="form-control" @bind="@_name" readonly />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<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>
|
||||||
|
@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>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
</TabPanel>
|
||||||
<Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
|
</TabStrip>
|
||||||
<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,x200,200x)" 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>
|
|
||||||
<PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="@(PermissionNames.Browse + "," + PermissionNames.View + "," + PermissionNames.Edit)" Permissions="@_permissions" @ref="_permissionGrid" />
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
|
<br />
|
||||||
@if (!_isSystem)
|
@if (!_isSystem)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveFolder">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveFolder">@SharedLocalizer["Save"]</button>
|
||||||
@@ -80,12 +108,6 @@
|
|||||||
@((MarkupString)" ")
|
@((MarkupString)" ")
|
||||||
<ActionDialog Header="Delete Folder" Message="Are You Sure You Wish To Delete This Folder?" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFolder())" ResourceKey="DeleteFolder" />
|
<ActionDialog Header="Delete Folder" Message="Are You Sure You Wish To Delete This Folder?" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFolder())" ResourceKey="DeleteFolder" />
|
||||||
}
|
}
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
@if (PageState.QueryString.ContainsKey("id"))
|
|
||||||
{
|
|
||||||
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -96,10 +118,11 @@
|
|||||||
private int _parentId = -1;
|
private int _parentId = -1;
|
||||||
private string _name;
|
private string _name;
|
||||||
private string _type = FolderTypes.Private;
|
private string _type = FolderTypes.Private;
|
||||||
private string _imagesizes = string.Empty;
|
|
||||||
private string _capacity = "0";
|
private string _capacity = "0";
|
||||||
|
private string _cachecontrol = string.Empty;
|
||||||
|
private string _imagesizes = string.Empty;
|
||||||
private bool _isSystem;
|
private bool _isSystem;
|
||||||
private string _permissions = string.Empty;
|
private List<Permission> _permissions = null;
|
||||||
private string _createdBy;
|
private string _createdBy;
|
||||||
private DateTime _createdOn;
|
private DateTime _createdOn;
|
||||||
private string _modifiedBy;
|
private string _modifiedBy;
|
||||||
@@ -128,10 +151,11 @@
|
|||||||
_parentId = folder.ParentId ?? -1;
|
_parentId = folder.ParentId ?? -1;
|
||||||
_name = folder.Name;
|
_name = folder.Name;
|
||||||
_type = folder.Type;
|
_type = folder.Type;
|
||||||
_imagesizes = folder.ImageSizes;
|
|
||||||
_capacity = folder.Capacity.ToString();
|
_capacity = folder.Capacity.ToString();
|
||||||
|
_cachecontrol = folder.CacheControl;
|
||||||
|
_imagesizes = folder.ImageSizes;
|
||||||
_isSystem = folder.IsSystem;
|
_isSystem = folder.IsSystem;
|
||||||
_permissions = folder.Permissions;
|
_permissions = folder.PermissionList;
|
||||||
_createdBy = folder.CreatedBy;
|
_createdBy = folder.CreatedBy;
|
||||||
_createdOn = folder.CreatedOn;
|
_createdOn = folder.CreatedOn;
|
||||||
_modifiedBy = folder.ModifiedBy;
|
_modifiedBy = folder.ModifiedBy;
|
||||||
@@ -171,6 +195,7 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Folder folder;
|
Folder folder;
|
||||||
|
|
||||||
if (_folderId != -1)
|
if (_folderId != -1)
|
||||||
{
|
{
|
||||||
folder = await FolderService.GetFolderAsync(_folderId);
|
folder = await FolderService.GetFolderAsync(_folderId);
|
||||||
@@ -180,8 +205,6 @@
|
|||||||
folder = new Folder();
|
folder = new Folder();
|
||||||
}
|
}
|
||||||
|
|
||||||
folder.SiteId = PageState.Site.SiteId;
|
|
||||||
|
|
||||||
if (_parentId == -1)
|
if (_parentId == -1)
|
||||||
{
|
{
|
||||||
folder.ParentId = null;
|
folder.ParentId = null;
|
||||||
@@ -191,12 +214,21 @@
|
|||||||
folder.ParentId = _parentId;
|
folder.ParentId = _parentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for duplicate folder names
|
||||||
|
if (_folders.Any(item => item.ParentId == folder.ParentId && item.Name == _name && item.FolderId != _folderId))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Folder.Duplicate"], MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
folder.SiteId = PageState.Site.SiteId;
|
||||||
folder.Name = _name;
|
folder.Name = _name;
|
||||||
folder.Type = _type;
|
folder.Type = _type;
|
||||||
folder.ImageSizes = _imagesizes;
|
|
||||||
folder.Capacity = int.Parse(_capacity);
|
folder.Capacity = int.Parse(_capacity);
|
||||||
|
folder.CacheControl = _cachecontrol;
|
||||||
|
folder.ImageSizes = _imagesizes;
|
||||||
folder.IsSystem = _isSystem;
|
folder.IsSystem = _isSystem;
|
||||||
folder.Permissions = _permissionGrid.GetPermissions();
|
folder.PermissionList = _permissionGrid.GetPermissionList();
|
||||||
|
|
||||||
if (_folderId != -1)
|
if (_folderId != -1)
|
||||||
{
|
{
|
||||||
@@ -209,7 +241,6 @@
|
|||||||
|
|
||||||
if (folder != null)
|
if (folder != null)
|
||||||
{
|
{
|
||||||
await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId);
|
|
||||||
await logger.LogInformation("Folder Saved {Folder}", folder);
|
await logger.LogInformation("Folder Saved {Folder}", folder);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
@@ -245,17 +276,9 @@
|
|||||||
}
|
}
|
||||||
if (!isparent)
|
if (!isparent)
|
||||||
{
|
{
|
||||||
var files = await FileService.GetFilesAsync(_folderId);
|
await FolderService.DeleteFolderAsync(_folderId);
|
||||||
if (files.Count == 0)
|
await logger.LogInformation("Folder Deleted {Folder}", _folderId);
|
||||||
{
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
await FolderService.DeleteFolderAsync(_folderId);
|
|
||||||
await logger.LogInformation("Folder Deleted {Folder}", _folderId);
|
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Folder.Files.InvalidDelete"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,53 +3,92 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (_files != null)
|
@if (_files == null)
|
||||||
{
|
{
|
||||||
<div class="container">
|
<p>
|
||||||
<div class="row mb-1 align-items-center">
|
<em>@SharedLocalizer["Loading"]</em>
|
||||||
<div class="col-sm-2">
|
</p>
|
||||||
<label class="control-label">@Localizer["Folder"] </label>
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<TabStrip>
|
||||||
|
<TabPanel Name="Files" Heading="Files" ResourceKey="Files">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md mb-1">
|
||||||
|
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
|
||||||
|
</div>
|
||||||
|
<div 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-sm-6">
|
|
||||||
<select class="form-select" @onchange="(e => FolderChanged(e))">
|
@if (_files.Count != 0)
|
||||||
@foreach (Folder folder in _folders)
|
{
|
||||||
{
|
<Pager Items="@_files" SearchProperties="Name">
|
||||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
<Header>
|
||||||
}
|
<th style="width: 1px;"> </th>
|
||||||
</select>
|
<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>@UtcToLocal(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>
|
</div>
|
||||||
<div class="col-sm-4">
|
<br />
|
||||||
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
|
</TabPanel>
|
||||||
<ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
|
</TabStrip>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Pager Items="@_files">
|
|
||||||
<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>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -57,6 +96,10 @@
|
|||||||
private int _folderId = -1;
|
private int _folderId = -1;
|
||||||
private List<File> _files;
|
private List<File> _files;
|
||||||
|
|
||||||
|
private string _imageFiles = string.Empty;
|
||||||
|
private string _uploadableFiles = string.Empty;
|
||||||
|
private int _maxChunkSize = 1;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
@@ -70,6 +113,13 @@
|
|||||||
_folderId = _folders[0].FolderId;
|
_folderId = _folders[0].FolderId;
|
||||||
await GetFiles();
|
await GetFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
||||||
|
_imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles;
|
||||||
|
_uploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles);
|
||||||
|
_uploadableFiles = (string.IsNullOrEmpty(_uploadableFiles)) ? Constants.UploadableFiles : _uploadableFiles;
|
||||||
|
_maxChunkSize = int.Parse(SettingService.GetSetting(settings, "MaxChunkSize", "1"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -114,4 +164,23 @@
|
|||||||
AddModuleMessage(string.Format(Localizer["Error.File.Delete"], file.Name), MessageType.Error);
|
AddModuleMessage(string.Format(Localizer["Error.File.Delete"], file.Name), MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task SaveSiteSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "ImageFiles", (_imageFiles != Constants.ImageFiles) ? _imageFiles.Replace(" ", "") : "", false);
|
||||||
|
settings = SettingService.SetSetting(settings, "UploadableFiles", (_uploadableFiles != Constants.UploadableFiles) ? _uploadableFiles.Replace(" ", "") : "", false);
|
||||||
|
settings = SettingService.SetSetting(settings, "MaxChunkSize", _maxChunkSize.ToString(), false);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
19
Oqtane.Client/Modules/Admin/Files/ModuleInfo.cs
Normal file
19
Oqtane.Client/Modules/Admin/Files/ModuleInfo.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Oqtane.Documentation;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Modules.Admin.Files
|
||||||
|
{
|
||||||
|
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
|
||||||
|
public class ModuleInfo : IModule
|
||||||
|
{
|
||||||
|
public ModuleDefinition ModuleDefinition => new ModuleDefinition
|
||||||
|
{
|
||||||
|
Name = "File Management",
|
||||||
|
Description = "File Management",
|
||||||
|
Version = Constants.Version,
|
||||||
|
Categories = "Admin",
|
||||||
|
ServerManagerType = "Oqtane.Modules.Admin.Files.Manager.FileManager, Oqtane.Server"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
|
<Label Class="col-sm-3" For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="runs-every" class="form-control" @bind="@_interval" maxlength="4" required />
|
<input id="runs-every" class="form-control mb-1" @bind="@_interval" maxlength="4" required />
|
||||||
<select id="runs-every" class="form-select" @bind="@_frequency" required>
|
<select id="runs-every" class="form-select" @bind="@_frequency" required>
|
||||||
<option value="m">@Localizer["Minute(s)"]</option>
|
<option value="m">@Localizer["Minute(s)"]</option>
|
||||||
<option value="H">@Localizer["Hour(s)"]</option>
|
<option value="H">@Localizer["Hour(s)"]</option>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
|
<Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="retention" class="form-control" @bind="@_retentionHistory" maxlength="4" required />
|
<input id="retention" type="number" min="0" step ="1" class="form-control" @bind="@_retentionHistory" maxlength="3" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
<input id="starting" type="date" class="form-control" @bind="@_startDate" />
|
<input id="starting" type="date" class="form-control" @bind="@_startDate" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input id="starting" type="text" class="form-control" placeholder="hh:mm" @bind="@_startTime" />
|
<input id="starting" type="time" class="form-control" placeholder="hh:mm" @bind="@_startTime" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<input id="ending" type="date" class="form-control" @bind="@_endDate" />
|
<input id="ending" type="date" class="form-control" @bind="@_endDate" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input id="ending" type="text" class="form-control" placeholder="hh:mm" @bind="@_endTime" />
|
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
<input id="next" type="date" class="form-control" @bind="@_nextDate" />
|
<input id="next" type="date" class="form-control" @bind="@_nextDate" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input id="next" type="text" class="form-control" placeholder="hh:mm" @bind="@_nextTime" />
|
<input id="next" type="time" class="form-control" placeholder="hh:mm" @bind="@_nextTime" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,22 +97,22 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
private int _jobId;
|
private int _jobId;
|
||||||
private string _name = string.Empty;
|
private string _name = string.Empty;
|
||||||
private string _jobType = string.Empty;
|
private string _jobType = string.Empty;
|
||||||
private string _isEnabled = "True";
|
private string _isEnabled = "True";
|
||||||
private string _interval = string.Empty;
|
private string _interval = string.Empty;
|
||||||
private string _frequency = string.Empty;
|
private string _frequency = string.Empty;
|
||||||
private DateTime? _startDate = null;
|
private DateTime? _startDate = null;
|
||||||
private string _startTime = string.Empty;
|
private DateTime? _startTime = null;
|
||||||
private DateTime? _endDate = null;
|
private DateTime? _endDate = null;
|
||||||
private string _endTime = string.Empty;
|
private DateTime? _endTime = null;
|
||||||
private string _retentionHistory = string.Empty;
|
private string _retentionHistory = string.Empty;
|
||||||
private DateTime? _nextDate = null;
|
private DateTime? _nextDate = null;
|
||||||
private string _nextTime = string.Empty;
|
private DateTime? _nextTime = null;
|
||||||
private string createdby;
|
private string createdby;
|
||||||
private DateTime createdon;
|
private DateTime createdon;
|
||||||
private string modifiedby;
|
private string modifiedby;
|
||||||
private DateTime modifiedon;
|
private DateTime modifiedon;
|
||||||
@@ -132,23 +132,14 @@
|
|||||||
_isEnabled = job.IsEnabled.ToString();
|
_isEnabled = job.IsEnabled.ToString();
|
||||||
_interval = job.Interval.ToString();
|
_interval = job.Interval.ToString();
|
||||||
_frequency = job.Frequency;
|
_frequency = job.Frequency;
|
||||||
_startDate = job.StartDate;
|
_startDate = UtcToLocal(job.StartDate);
|
||||||
if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
|
_startTime = UtcToLocal(job.StartDate);
|
||||||
{
|
_endDate = UtcToLocal(job.EndDate);
|
||||||
_startTime = job.StartDate.Value.ToString("HH:mm");
|
_endTime = UtcToLocal(job.EndDate);
|
||||||
}
|
_retentionHistory = job.RetentionHistory.ToString();
|
||||||
_endDate = job.EndDate;
|
_nextDate = UtcToLocal(job.NextExecution);
|
||||||
if (job.EndDate != null && job.EndDate.Value.TimeOfDay.TotalSeconds != 0)
|
_nextTime = UtcToLocal(job.NextExecution);
|
||||||
{
|
createdby = job.CreatedBy;
|
||||||
_endTime = job.EndDate.Value.ToString("HH:mm");
|
|
||||||
}
|
|
||||||
_retentionHistory = job.RetentionHistory.ToString();
|
|
||||||
_nextDate = job.NextExecution;
|
|
||||||
if (job.NextExecution != null && job.NextExecution.Value.TimeOfDay.TotalSeconds != 0)
|
|
||||||
{
|
|
||||||
_nextTime = job.NextExecution.Value.ToString("HH:mm");
|
|
||||||
}
|
|
||||||
createdby = job.CreatedBy;
|
|
||||||
createdon = job.CreatedOn;
|
createdon = job.CreatedOn;
|
||||||
modifiedby = job.ModifiedBy;
|
modifiedby = job.ModifiedBy;
|
||||||
modifiedon = job.ModifiedOn;
|
modifiedon = job.ModifiedOn;
|
||||||
@@ -163,6 +154,11 @@
|
|||||||
|
|
||||||
private async Task SaveJob()
|
private async Task SaveJob()
|
||||||
{
|
{
|
||||||
|
if (!Utilities.ValidateEffectiveExpiryDates(_startDate, _endDate))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
validated = true;
|
validated = true;
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
@@ -180,50 +176,27 @@
|
|||||||
{
|
{
|
||||||
job.Interval = int.Parse(_interval);
|
job.Interval = int.Parse(_interval);
|
||||||
}
|
}
|
||||||
job.StartDate = _startDate;
|
job.StartDate = LocalToUtc(_startDate.Value.Date.Add(_startTime.Value.TimeOfDay));
|
||||||
if (job.StartDate != null)
|
job.EndDate = LocalToUtc(_endDate.Value.Date.Add(_endTime.Value.TimeOfDay));
|
||||||
{
|
job.RetentionHistory = int.Parse(_retentionHistory);
|
||||||
job.StartDate = job.StartDate.Value.Date;
|
job.NextExecution = LocalToUtc(_nextDate.Value.Date.Add(_nextTime.Value.TimeOfDay));
|
||||||
if (!string.IsNullOrEmpty(_startTime))
|
|
||||||
{
|
try
|
||||||
job.StartDate = DateTime.Parse(job.StartDate.Value.ToShortDateString() + " " + _startTime);
|
{
|
||||||
}
|
job = await JobService.UpdateJobAsync(job);
|
||||||
}
|
await logger.LogInformation("Job Updated {Job}", job);
|
||||||
job.EndDate = _endDate;
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
if (job.EndDate != null)
|
}
|
||||||
{
|
catch (Exception ex)
|
||||||
job.EndDate = job.EndDate.Value.Date;
|
{
|
||||||
if (!string.IsNullOrEmpty(_endTime))
|
await logger.LogError(ex, "Error Udate Job {Job} {Error}", job, ex.Message);
|
||||||
{
|
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
|
||||||
job.EndDate = DateTime.Parse(job.EndDate.Value.ToShortDateString() + " " + _endTime);
|
}
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
job.RetentionHistory = int.Parse(_retentionHistory);
|
{
|
||||||
job.NextExecution = _nextDate;
|
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
|
||||||
if (job.NextExecution != null)
|
}
|
||||||
{
|
}
|
||||||
job.NextExecution = job.NextExecution.Value.Date;
|
|
||||||
if (!string.IsNullOrEmpty(_nextTime))
|
|
||||||
{
|
|
||||||
job.NextExecution = DateTime.Parse(job.NextExecution.Value.ToShortDateString() + " " + _nextTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
job = await JobService.UpdateJobAsync(job);
|
|
||||||
await logger.LogInformation("Job Updated {Job}", job);
|
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Udate Job {Job} {Error}", job, ex.Message);
|
|
||||||
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,11 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ActionLink Action="Log" Class="btn btn-secondary" Text="View Logs" ResourceKey="ViewJobs" />
|
<button type="button" class="btn btn-secondary" @onclick="Refresh">@Localizer["Refresh.Text"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="(async () => await Refresh())">Refresh</button>
|
|
||||||
<br />
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<Pager Items="@_jobs">
|
<Pager Items="@_jobs" SearchProperties="Name">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
@@ -27,13 +24,12 @@ else
|
|||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="EditJob" /></td>
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="EditJob" /></td>
|
||||||
<td><ActionDialog Header="Delete Job" Message="Are You Sure You Wish To Delete This Job?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteJob(context))" ResourceKey="DeleteJob" /></td>
|
<td><ActionLink Action="Log" Text="Log" Class="btn btn-secondary" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="JobLog" /></td>
|
||||||
<td><ActionLink Action="Log" Class="btn btn-secondary" Parameters="@($"id=" + context.JobId.ToString())" ResourceKey="JobLog" /></td>
|
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
|
<td>@DisplayStatus(context.IsEnabled, context.IsExecuting)</td>
|
||||||
<td>@DisplayFrequency(context.Interval, context.Frequency)</td>
|
<td>@DisplayFrequency(context.Interval, context.Frequency)</td>
|
||||||
<td>@context.NextExecution</td>
|
<td>@UtcToLocal(context.NextExecution)</td>
|
||||||
<td>
|
<td>
|
||||||
@if (context.IsStarted)
|
@if (context.IsStarted)
|
||||||
{
|
{
|
||||||
@@ -46,17 +42,25 @@ else
|
|||||||
</td>
|
</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<ActionLink Action="Log" Class="btn btn-secondary" Text="View All Logs" ResourceKey="ViewLogs" />
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@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()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_jobs = await JobService.GetJobsAsync();
|
await GetJobs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task GetJobs()
|
||||||
|
{
|
||||||
|
_jobs = await JobService.GetJobsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private string DisplayStatus(bool isEnabled, bool isExecuting)
|
private string DisplayStatus(bool isEnabled, bool isExecuting)
|
||||||
{
|
{
|
||||||
@@ -108,22 +112,6 @@ else
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DeleteJob(Job job)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await JobService.DeleteJobAsync(job.JobId);
|
|
||||||
await logger.LogInformation("Job Deleted {Job}", job);
|
|
||||||
_jobs = await JobService.GetJobsAsync();
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Deleting Job {Job} {Error}", job, ex.Message);
|
|
||||||
AddModuleMessage(Localizer["Error.Job.Delete"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task StartJob(int jobId)
|
private async Task StartJob(int jobId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -160,7 +148,8 @@ else
|
|||||||
|
|
||||||
private async Task Refresh()
|
private async Task Refresh()
|
||||||
{
|
{
|
||||||
_jobs = await JobService.GetJobsAsync();
|
ShowProgressIndicator();
|
||||||
StateHasChanged();
|
await GetJobs();
|
||||||
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Refresh">@Localizer["Refresh"]</button>
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
<Pager Items="@_jobLogs">
|
<Pager Items="@_jobLogs">
|
||||||
<Header>
|
<Header>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
@@ -20,8 +23,8 @@ else
|
|||||||
<Row>
|
<Row>
|
||||||
<td>@context.Job.Name</td>
|
<td>@context.Job.Name</td>
|
||||||
<td>@DisplayStatus(context.Job.IsExecuting, context.Succeeded)</td>
|
<td>@DisplayStatus(context.Job.IsExecuting, context.Succeeded)</td>
|
||||||
<td>@context.StartDate</td>
|
<td>@UtcToLocal(context.StartDate)</td>
|
||||||
<td>@context.FinishDate</td>
|
<td>@UtcToLocal(context.FinishDate)</td>
|
||||||
</Row>
|
</Row>
|
||||||
<Detail>
|
<Detail>
|
||||||
<td colspan="4">@((MarkupString)context.Notes)</td>
|
<td colspan="4">@((MarkupString)context.Notes)</td>
|
||||||
@@ -36,14 +39,17 @@ else
|
|||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
_jobLogs = await JobLogService.GetJobLogsAsync();
|
await GetJobLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetJobLogs()
|
||||||
|
{
|
||||||
|
var jobId = -1;
|
||||||
if (PageState.QueryString.ContainsKey("id"))
|
if (PageState.QueryString.ContainsKey("id"))
|
||||||
{
|
{
|
||||||
_jobLogs = _jobLogs.Where(item => item.JobId == Int32.Parse(PageState.QueryString["id"])).ToList();
|
jobId = int.Parse(PageState.QueryString["id"]);
|
||||||
}
|
}
|
||||||
|
_jobLogs = await JobLogService.GetJobLogsAsync(jobId);
|
||||||
_jobLogs = _jobLogs.OrderByDescending(item => item.JobLogId).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string DisplayStatus(bool isExecuting, bool? succeeded)
|
private string DisplayStatus(bool isExecuting, bool? succeeded)
|
||||||
@@ -67,4 +73,11 @@ else
|
|||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task Refresh()
|
||||||
|
{
|
||||||
|
ShowProgressIndicator();
|
||||||
|
await GetJobLogs();
|
||||||
|
HideProgressIndicator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Languages
|
@namespace Oqtane.Modules.Admin.Languages
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@using System.Globalization
|
@using System.Globalization
|
||||||
@using Microsoft.AspNetCore.Localization
|
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject ILocalizationService LocalizationService
|
@inject ILocalizationService LocalizationService
|
||||||
@inject ILanguageService LanguageService
|
@inject ILanguageService LanguageService
|
||||||
@@ -9,338 +8,119 @@
|
|||||||
@inject IStringLocalizer<Add> Localizer
|
@inject IStringLocalizer<Add> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (_supportedCultures == null)
|
@if (_cultures == null)
|
||||||
{
|
{
|
||||||
<p><em>@SharedLocalizer["Loading"]</em></p>
|
<p><em>@SharedLocalizer["Loading"]</em></p>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Manage" ResourceKey="Manage">
|
<TabPanel Name="Manage" ResourceKey="Manage" Heading="Manage">
|
||||||
@if (_availableCultures.Count() == 0)
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
{
|
<div class="container">
|
||||||
<ModuleMessage Type="MessageType.Info" Message="@_message"></ModuleMessage>
|
<div class="row mb-1 align-items-center">
|
||||||
}
|
<Label Class="col-sm-3" For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label>
|
||||||
else
|
<div class="col-sm-9">
|
||||||
{
|
<select id="_code" class="form-select" @bind="@_code" required>
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<option value="-"><@SharedLocalizer["Not Specified"]></option>
|
||||||
<div class="container">
|
@foreach (var culture in _cultures)
|
||||||
<div class="row mb-1 align-items-center">
|
{
|
||||||
<Label Class="col-sm-3" For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label>
|
<option value="@culture.Name">@(culture.DisplayName + " (" + culture.Name + ")")</option>
|
||||||
<div class="col-sm-9">
|
}
|
||||||
<select id="_code" class="form-select" @bind="@_code" required>
|
</select>
|
||||||
@foreach (var culture in _availableCultures)
|
|
||||||
{
|
|
||||||
<option value="@culture.Name">@culture.DisplayName</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="default" class="form-select" @bind="@_isDefault" required>
|
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
|
<div class="row mb-1 align-items-center">
|
||||||
</form>
|
<Label Class="col-sm-3" For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
|
||||||
}
|
<div class="col-sm-9">
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<select id="default" class="form-select" @bind="@_default" required>
|
||||||
</TabPanel>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
<TabPanel Name="Download" ResourceKey="Download" Security="SecurityAccessLevel.Host">
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
<div class="row justify-content-center mb-3">
|
</select>
|
||||||
<div class="col-sm-6">
|
</div>
|
||||||
<div class="input-group">
|
|
||||||
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
|
|
||||||
<option value="free">@SharedLocalizer["Free"]</option>
|
|
||||||
<option value="paid">@SharedLocalizer["Paid"]</option>
|
|
||||||
</select>
|
|
||||||
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
@if (_packages != null)
|
</form>
|
||||||
{
|
|
||||||
@if (_packages.Count > 0)
|
|
||||||
{
|
|
||||||
<Pager Items="@_packages">
|
|
||||||
<Row>
|
|
||||||
<td>
|
|
||||||
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3> by: <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
|
|
||||||
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
|
||||||
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"] |
|
|
||||||
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong> |
|
|
||||||
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
|
|
||||||
@((MarkupString)(context.TrialPeriod > 0 ? " | <strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
|
|
||||||
</td>
|
|
||||||
<td style="width: 1px; vertical-align: middle;">
|
|
||||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
<td style="width: 1px; vertical-align: middle;">
|
|
||||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
|
|
||||||
{
|
|
||||||
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</Row>
|
|
||||||
</Pager>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<br />
|
|
||||||
<div class="mx-auto text-center">
|
|
||||||
@Localizer["Search.NoResults"]
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<button type="button" class="btn btn-success" @onclick="InstallLanguages">@SharedLocalizer["Install"]</button>
|
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
|
||||||
}
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
|
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host" Heading="Upload">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install to complete the installation." ResourceKey="LanguageUpload">Language: </Label>
|
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install." ResourceKey="LanguageUpload">Translation: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
|
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-success" @onclick="InstallLanguages">@SharedLocalizer["Install"]</button>
|
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (_productname != "")
|
|
||||||
{
|
|
||||||
<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">@SharedLocalizer["Review License Terms"]</h5>
|
|
||||||
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p style="height: 200px; overflow-y: scroll;">
|
|
||||||
<h3>@_productname</h3>
|
|
||||||
@if (!string.IsNullOrEmpty(_license))
|
|
||||||
{
|
|
||||||
@((MarkupString)_license)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@SharedLocalizer["License Not Specified"]
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button>
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
|
|
||||||
private string _code = string.Empty;
|
private string _code = "-";
|
||||||
private string _isDefault = "False";
|
private string _default = "False";
|
||||||
private string _message;
|
private List<string> _languages;
|
||||||
private IEnumerable<Culture> _supportedCultures;
|
private IEnumerable<Culture> _cultures;
|
||||||
private IEnumerable<Culture> _availableCultures;
|
|
||||||
private List<Package> _packages;
|
|
||||||
private string _price = "free";
|
|
||||||
private string _search = "";
|
|
||||||
private string _productname = "";
|
|
||||||
private string _license = "";
|
|
||||||
private string _packageid = "";
|
|
||||||
private string _version = "";
|
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||||
var languagesCodes = languages.Select(l => l.Code).ToList();
|
_languages = languages.Select(l => l.Code).ToList();
|
||||||
|
|
||||||
_supportedCultures = await LocalizationService.GetCulturesAsync();
|
await LoadCultures();
|
||||||
_availableCultures = _supportedCultures
|
}
|
||||||
.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !languagesCodes.Contains(c.Name));
|
|
||||||
await LoadTranslations();
|
|
||||||
|
|
||||||
if (_supportedCultures.Count() == 1)
|
private async Task LoadCultures()
|
||||||
{
|
{
|
||||||
_message = Localizer["OnlyEnglish"];
|
_cultures = await LocalizationService.GetCulturesAsync(false);
|
||||||
}
|
_cultures = _cultures.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !_languages.Contains(c.Name));
|
||||||
else if (_availableCultures.Count() == 0)
|
_code = "-";
|
||||||
{
|
}
|
||||||
_message = Localizer["AllLanguages"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadTranslations()
|
private async Task SaveLanguage()
|
||||||
{
|
{
|
||||||
_packages = await PackageService.GetPackagesAsync("translation", _search, _price, "");
|
validated = true;
|
||||||
}
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form) && _code != "-")
|
||||||
|
{
|
||||||
|
var language = new Language
|
||||||
|
{
|
||||||
|
SiteId = PageState.Page.SiteId,
|
||||||
|
Code = _code,
|
||||||
|
IsDefault = (_default == null ? false : Boolean.Parse(_default))
|
||||||
|
};
|
||||||
|
|
||||||
private async void PriceChanged(ChangeEventArgs e)
|
try
|
||||||
{
|
{
|
||||||
try
|
language = await LanguageService.AddLanguageAsync(language);
|
||||||
{
|
|
||||||
_price = (string)e.Value;
|
|
||||||
_search = "";
|
|
||||||
await LoadTranslations();
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error On PriceChanged");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Search()
|
if (language.IsDefault)
|
||||||
{
|
{
|
||||||
try
|
await SetCultureAsync(language.Code);
|
||||||
{
|
}
|
||||||
await LoadTranslations();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error On Search");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Reset()
|
await logger.LogInformation("Language Added {Language}", language);
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_search = "";
|
|
||||||
await LoadTranslations();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error On Reset");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveLanguage()
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
{
|
}
|
||||||
validated = true;
|
catch (Exception ex)
|
||||||
var interop = new Interop(JSRuntime);
|
{
|
||||||
if (await interop.FormValid(form))
|
await logger.LogError(ex, "Error Adding Language {Language} {Error}", language, ex.Message);
|
||||||
{
|
AddModuleMessage(Localizer["Error.Language.Add"], MessageType.Error);
|
||||||
var language = new Language
|
}
|
||||||
{
|
}
|
||||||
SiteId = PageState.Page.SiteId,
|
else
|
||||||
Name = CultureInfo.GetCultureInfo(_code).DisplayName,
|
{
|
||||||
Code = _code,
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
IsDefault = (_isDefault == null ? false : Boolean.Parse(_isDefault))
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
language = await LanguageService.AddLanguageAsync(language);
|
|
||||||
|
|
||||||
if (language.IsDefault)
|
|
||||||
{
|
|
||||||
await SetCultureAsync(language.Code);
|
|
||||||
}
|
|
||||||
|
|
||||||
await logger.LogInformation("Language Added {Language}", language);
|
|
||||||
|
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Adding Language {Language} {Error}", language, ex.Message);
|
|
||||||
AddModuleMessage(Localizer["Error.Language.Add"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void HideModal()
|
|
||||||
{
|
|
||||||
_productname = "";
|
|
||||||
_license = "";
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task GetPackage(string packageid, string version)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var package = await PackageService.GetPackageAsync(packageid, version);
|
|
||||||
if (package != null)
|
|
||||||
{
|
|
||||||
_productname = package.Name;
|
|
||||||
if (!string.IsNullOrEmpty(package.License))
|
|
||||||
{
|
|
||||||
_license = package.License.Replace("\n", "<br />");
|
|
||||||
}
|
|
||||||
_packageid = package.PackageId;
|
|
||||||
_version = package.Version;
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
|
|
||||||
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DownloadPackage()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
|
|
||||||
await logger.LogInformation("Language Package {Name} {Version} Downloaded Successfully", _packageid, _version);
|
|
||||||
AddModuleMessage(Localizer["Success.Language.Download"], MessageType.Success);
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Downloading Translation {Name} {Version}", _packageid, _version);
|
|
||||||
AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InstallLanguages()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await PackageService.InstallPackagesAsync();
|
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Language.Install"], NavigateUrl("admin/system")), MessageType.Success);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Installing Translations");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SetCultureAsync(string culture)
|
private async Task SetCultureAsync(string culture)
|
||||||
{
|
{
|
||||||
@@ -348,9 +128,12 @@ else
|
|||||||
{
|
{
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
||||||
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
|
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
|
||||||
|
|
||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnUpload()
|
||||||
|
{
|
||||||
|
AddModuleMessage(string.Format(Localizer["Success.Language.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
109
Oqtane.Client/Modules/Admin/Languages/Edit.razor
Normal file
109
Oqtane.Client/Modules/Admin/Languages/Edit.razor
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Languages
|
||||||
|
@inherits ModuleBase
|
||||||
|
@using System.Globalization
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject ILocalizationService LocalizationService
|
||||||
|
@inject ILanguageService LanguageService
|
||||||
|
@inject IPackageService PackageService
|
||||||
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
@if (_code == null)
|
||||||
|
{
|
||||||
|
<p><em>@SharedLocalizer["Loading"]</em></p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<TabStrip>
|
||||||
|
<TabPanel Name="Manage" ResourceKey="Manage" Heading="Manage">
|
||||||
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="code" class="form-control" @bind="@_code" readonly/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="default" class="form-select" @bind="@_default" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</form>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
private int _languageId = -1;
|
||||||
|
private string _code = string.Empty;
|
||||||
|
private string _cultureName = string.Empty;
|
||||||
|
private string _default = "False";
|
||||||
|
private List<Language> _languages;
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_languageId = Int32.Parse(PageState.QueryString["id"]);
|
||||||
|
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId);
|
||||||
|
Language language = _languages.Where(x => x.LanguageId == _languageId).FirstOrDefault();
|
||||||
|
if (language != null)
|
||||||
|
{
|
||||||
|
_code = language.Code;
|
||||||
|
_cultureName = language.Name;
|
||||||
|
_default = language.IsDefault.ToString();
|
||||||
|
if (language.SiteId == null)
|
||||||
|
{
|
||||||
|
language.SiteId = PageState.Site.SiteId;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveLanguage()
|
||||||
|
{
|
||||||
|
Language language = _languages.Where(x => x.LanguageId == _languageId).FirstOrDefault();
|
||||||
|
if (language != null)
|
||||||
|
{
|
||||||
|
language.IsDefault = Boolean.Parse(_default);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LanguageService.EditLanguageAsync(language);
|
||||||
|
|
||||||
|
if (language.IsDefault)
|
||||||
|
{
|
||||||
|
await SetCultureAsync(language.Code);
|
||||||
|
}
|
||||||
|
|
||||||
|
await logger.LogInformation("Language Edited {Language}", language);
|
||||||
|
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Editing Language {Language} {Error}", language, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Language.Edit"], MessageType.Error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetCultureAsync(string culture)
|
||||||
|
{
|
||||||
|
if (culture != CultureInfo.CurrentUICulture.Name)
|
||||||
|
{
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
||||||
|
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -14,44 +14,107 @@ else
|
|||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Add Language" ResourceKey="AddLanguage" />
|
<ActionLink Action="Add" Text="Add Language" ResourceKey="AddLanguage" />
|
||||||
|
|
||||||
<Pager Items="@_languages">
|
<Pager Items="@_languages" SearchProperties="Name,Code">
|
||||||
<Header>
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
<th>@Localizer["Code"]</th>
|
<th>@Localizer["Code"]</th>
|
||||||
<th>@Localizer["Default"]</th>
|
<th>@Localizer["Default"]</th>
|
||||||
<th style="width: 1px;"> </th>
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
<th style="width: 1px;">@Localizer["Translation"]</th>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
}
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
|
<td>
|
||||||
|
@if (!context.IsDefault)
|
||||||
|
{
|
||||||
|
<ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.LanguageId.ToString())" ResourceKey="EditLanguage" />
|
||||||
|
}
|
||||||
|
</td>
|
||||||
<td><ActionDialog Header="Delete Language" Message="@string.Format(Localizer["Confirm.Language.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@((context.IsDefault && _languages.Count > 2) || context.Code == Constants.DefaultCulture)" ResourceKey="DeleteLanguage" /></td>
|
<td><ActionDialog Header="Delete Language" Message="@string.Format(Localizer["Confirm.Language.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@((context.IsDefault && _languages.Count > 2) || context.Code == Constants.DefaultCulture)" ResourceKey="DeleteLanguage" /></td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
<td>@context.Code</td>
|
<td>@context.Code</td>
|
||||||
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
|
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
|
||||||
<td>
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
@if (UpgradeAvailable(context.Code))
|
{
|
||||||
{
|
<td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</td>
|
||||||
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@SharedLocalizer["Upgrade"]</button>
|
<td>
|
||||||
}
|
@{
|
||||||
</td>
|
var translation = TranslationAvailable(context.Code, context.Version);
|
||||||
|
}
|
||||||
|
@if (translation != null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(context.Version))
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(context.Code, translation.Version))>@SharedLocalizer["Download"]</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Version.Parse(translation.Version).CompareTo(Version.Parse(context.Version)) > 0)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(context.Code, translation.Version))>@SharedLocalizer["Upgrade"]</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
}
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (_package != null)
|
||||||
|
{
|
||||||
|
<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">@SharedLocalizer["Review License Terms"]</h5>
|
||||||
|
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p style="height: 200px; overflow-y: scroll;">
|
||||||
|
<h4 style="display: inline;"><a href="@_package.ProductUrl" target="_new">@_package.Name</a></h4><br />
|
||||||
|
@SharedLocalizer["Search.By"]: <strong><a href="@_package.OwnerUrl" target="new">@_package.Owner</a></strong><br />
|
||||||
|
@(_package.Description.Length > 400 ? (_package.Description.Substring(0, 400) + "...") : _package.Description)<br />
|
||||||
|
<strong>@(String.Format("{0:n0}", _package.Downloads))</strong> @SharedLocalizer["Search.Downloads"] |
|
||||||
|
@SharedLocalizer["Search.Released"]: <strong>@_package.ReleaseDate.ToString("MMM dd, yyyy")</strong> |
|
||||||
|
@SharedLocalizer["Search.Version"]: <strong>@_package.Version</strong>
|
||||||
|
@((MarkupString)(!string.IsNullOrEmpty(_package.PackageUrl) ? " | " + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(_package.PackageUrl).Host + "</strong>" : ""))
|
||||||
|
<br /><br />
|
||||||
|
@if (!string.IsNullOrEmpty(_package.License))
|
||||||
|
{
|
||||||
|
@((MarkupString)_package.License.Replace("\n", "<br />"))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@SharedLocalizer["License Not Specified"]
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Language> _languages;
|
private List<Language> _languages;
|
||||||
private List<Package> _packages;
|
private List<Package> _packages;
|
||||||
|
private Package _package;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
await GetLanguages();
|
||||||
|
|
||||||
var cultures = await LocalizationService.GetCulturesAsync();
|
|
||||||
var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture));
|
|
||||||
|
|
||||||
// Adds English as default language
|
|
||||||
_languages.Insert(0, new Language { Name = culture.DisplayName, Code = culture.Name, IsDefault = !_languages.Any(l => l.IsDefault) });
|
|
||||||
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
@@ -59,13 +122,18 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task GetLanguages()
|
||||||
|
{
|
||||||
|
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task DeleteLanguage(Language language)
|
private async Task DeleteLanguage(Language language)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LanguageService.DeleteLanguageAsync(language.LanguageId);
|
await LanguageService.DeleteLanguageAsync(language.LanguageId);
|
||||||
await logger.LogInformation("Language Deleted {Language}", language);
|
await logger.LogInformation("Language Deleted {Language}", language);
|
||||||
|
await GetLanguages();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -76,37 +144,53 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool UpgradeAvailable(string code)
|
private Package TranslationAvailable(string code, string version)
|
||||||
{
|
{
|
||||||
var upgradeavailable = false;
|
return _packages?.FirstOrDefault(item => item.PackageId == (Constants.PackageId + "." + code));
|
||||||
if (_packages != null)
|
|
||||||
{
|
|
||||||
var package = _packages.Where(item => item.PackageId == (Constants.PackageId + ".Client." + code)).FirstOrDefault();
|
|
||||||
if (package != null)
|
|
||||||
{
|
|
||||||
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return upgradeavailable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DownloadLanguage(string code)
|
private async Task GetPackage(string code, string version)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
_package = await PackageService.GetPackageAsync(Constants.PackageId + "." + code, version, false);
|
||||||
|
if (_package != null)
|
||||||
{
|
{
|
||||||
await PackageService.DownloadPackageAsync(Constants.PackageId + ".Client." + code, Constants.Version, Constants.PackagesFolder);
|
StateHasChanged();
|
||||||
await logger.LogInformation("Translation Downloaded {Code} {Version}", code, Constants.Version);
|
}
|
||||||
await PackageService.InstallPackagesAsync();
|
else
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Language.Install"], NavigateUrl("admin/system")), MessageType.Success);
|
{
|
||||||
|
await logger.LogError("Error Getting Package {PackageId} {Version}", Constants.PackageId + "." + code, Constants.Version);
|
||||||
|
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Downloading Translation {Code} {Version} {Error}", code, Constants.Version, ex.Message);
|
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", Constants.PackageId + "." + code, Constants.Version);
|
||||||
AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task DownloadPackage()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version);
|
||||||
|
await logger.LogInformation("Language Package {Name} {Version} Downloaded Successfully", _package.PackageId, _package.Version);
|
||||||
|
AddModuleMessage(string.Format(Localizer["Success.Language.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
|
_package = null;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Downloading Translation {Name} {Version}", _package.PackageId, _package.Version);
|
||||||
|
AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HideModal()
|
||||||
|
{
|
||||||
|
_package = null;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,315 +1,340 @@
|
|||||||
|
@using System.Net
|
||||||
@namespace Oqtane.Modules.Admin.Login
|
@namespace Oqtane.Modules.Admin.Login
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IServiceProvider ServiceProvider
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject SiteState SiteState
|
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<AuthorizeView Roles="@RoleNames.Registered">
|
@if (PageState.User != null)
|
||||||
<Authorizing>
|
{
|
||||||
<text>...</text>
|
<ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
|
||||||
</Authorizing>
|
}
|
||||||
<Authorized>
|
else
|
||||||
<div>@Localizer["Info.SignedIn"]</div>
|
{
|
||||||
</Authorized>
|
@if (!twofactor)
|
||||||
<NotAuthorized>
|
{
|
||||||
@if (!twofactor)
|
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
{
|
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
||||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
@if (_allowexternallogin)
|
||||||
<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 />
|
||||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
|
||||||
<br /><br />
|
<br />
|
||||||
}
|
}
|
||||||
@if (_allowsitelogin)
|
@if (_allowsitelogin)
|
||||||
{
|
{
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
||||||
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" required />
|
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mt-2">
|
<div class="form-group mt-2">
|
||||||
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" required />
|
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mt-2">
|
<div class="form-group mt-2">
|
||||||
<div class="form-check">
|
@if (!_alwaysremember)
|
||||||
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
|
{
|
||||||
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
|
<div class="form-check">
|
||||||
</div>
|
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
|
||||||
</div>
|
<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>
|
||||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
</div>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
}
|
||||||
<br /><br />
|
</div>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||||
}
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
</div>
|
<br />
|
||||||
</form>
|
|
||||||
}
|
<br />
|
||||||
else
|
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||||
{
|
@if (PageState.Site.AllowRegistration)
|
||||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
{
|
||||||
<div class="container Oqtane-Modules-Admin-Login">
|
<br />
|
||||||
<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>
|
<br />
|
||||||
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
|
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
||||||
</div>
|
}
|
||||||
<br />
|
}
|
||||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
</div>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Cancel"]</button>
|
</form>
|
||||||
</div>
|
}
|
||||||
</form>
|
else
|
||||||
}
|
{
|
||||||
</NotAuthorized>
|
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
</AuthorizeView>
|
<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 {
|
@code {
|
||||||
private bool _allowsitelogin = true;
|
private bool _allowsitelogin = true;
|
||||||
private bool _allowexternallogin = false;
|
private bool _allowexternallogin = false;
|
||||||
private ElementReference login;
|
private ElementReference login;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
private bool twofactor = false;
|
private bool twofactor = false;
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
private ElementReference username;
|
private ElementReference username;
|
||||||
private string _password = string.Empty;
|
private string _password = string.Empty;
|
||||||
private string _passwordtype = "password";
|
private string _passwordtype = "password";
|
||||||
private string _togglepassword = string.Empty;
|
private string _togglepassword = string.Empty;
|
||||||
private bool _remember = false;
|
private bool _remember = false;
|
||||||
private string _code = string.Empty;
|
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 SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
public override List<Resource> Resources => new List<Resource>()
|
||||||
|
|
||||||
public override List<Resource> Resources => new List<Resource>()
|
|
||||||
{
|
{
|
||||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
|
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||||
|
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||||
|
_alwaysremember = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AlwaysRemember", "false"));
|
||||||
|
|
||||||
if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]))
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
{
|
|
||||||
_allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"]))
|
if (PageState.QueryString.ContainsKey("name"))
|
||||||
{
|
{
|
||||||
_allowexternallogin = true;
|
_username = PageState.QueryString["name"];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("returnurl"))
|
if (PageState.QueryString.ContainsKey("token") && !string.IsNullOrEmpty(_username))
|
||||||
{
|
{
|
||||||
_returnUrl = PageState.QueryString["returnurl"];
|
var user = new User();
|
||||||
}
|
user.SiteId = PageState.Site.SiteId;
|
||||||
|
user.Username = _username;
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("name"))
|
if (PageState.QueryString.ContainsKey("key"))
|
||||||
{
|
{
|
||||||
_username = PageState.QueryString["name"];
|
user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
|
||||||
}
|
if (user != null)
|
||||||
|
{
|
||||||
|
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
|
||||||
|
AddModuleMessage(Localizer["Success.Account.Linked"], MessageType.Info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await logger.LogError(LogFunction.Security, "External Login Linkage Failed For Username {Username}", _username);
|
||||||
|
AddModuleMessage(Localizer["Message.Account.NotLinked"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
_username = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
await logger.LogInformation(LogFunction.Security, "Email Verified For Username {Username}", _username);
|
||||||
|
AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username);
|
||||||
|
AddModuleMessage(Localizer["Message.Account.NotVerified"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (PageState.QueryString.ContainsKey("status"))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading Login {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.LoadLogin"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("token") && !string.IsNullOrEmpty(_username))
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
var user = new User();
|
if (firstRender && PageState.User == null && _allowsitelogin)
|
||||||
user.SiteId = PageState.Site.SiteId;
|
{
|
||||||
user.Username = _username;
|
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
|
||||||
|
{
|
||||||
|
await username.FocusAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("key"))
|
// redirect logged in user to specified page
|
||||||
{
|
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
|
{
|
||||||
if (user != null)
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
{
|
}
|
||||||
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
|
}
|
||||||
AddModuleMessage(Localizer["Success.Account.Linked"], MessageType.Info);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await logger.LogError(LogFunction.Security, "External Login Linkage Failed For Username {Username}", _username);
|
|
||||||
AddModuleMessage(Localizer["Message.Account.NotLinked"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
_username = "";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username);
|
|
||||||
AddModuleMessage(Localizer["Success.Account.Verified"], MessageType.Info);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username);
|
|
||||||
AddModuleMessage(Localizer["Message.Account.NotVerified"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (PageState.QueryString.ContainsKey("status"))
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Loading Login {Error}", ex.Message);
|
|
||||||
AddModuleMessage(Localizer["Error.LoadLogin"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
private async Task Login()
|
||||||
{
|
{
|
||||||
if (firstRender && PageState.User == null)
|
try
|
||||||
{
|
{
|
||||||
await username.FocusAsync();
|
validated = true;
|
||||||
}
|
var interop = new Interop(JSRuntime);
|
||||||
}
|
if (await interop.FormValid(login))
|
||||||
|
{
|
||||||
|
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
|
||||||
|
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
|
||||||
|
|
||||||
private async Task Login()
|
if (!twofactor)
|
||||||
{
|
{
|
||||||
try
|
_remember = _alwaysremember || _remember;
|
||||||
{
|
user = await UserService.LoginUserAsync(user, hybrid, _remember);
|
||||||
validated = true;
|
}
|
||||||
var interop = new Interop(JSRuntime);
|
else
|
||||||
if (await interop.FormValid(login))
|
{
|
||||||
{
|
user = await UserService.VerifyTwoFactorAsync(user, _code);
|
||||||
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
|
}
|
||||||
|
|
||||||
if (!twofactor)
|
if (user != null && user.IsAuthenticated)
|
||||||
{
|
{
|
||||||
user = await UserService.LoginUserAsync(user);
|
await logger.LogInformation(LogFunction.Security, "Login Successful For {Username} From IP Address {IPAddress}", _username, SiteState.RemoteIPAddress);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
user = await UserService.VerifyTwoFactorAsync(user, _code);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.IsAuthenticated)
|
// return url is not specified if user navigated directly to login page
|
||||||
{
|
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
|
||||||
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
|
|
||||||
|
|
||||||
// post back to the Login page so that the cookies are set correctly
|
if (hybrid)
|
||||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
|
{
|
||||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
// hybrid apps utilize an interactive login
|
||||||
await interop.SubmitForm(url, fields);
|
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||||
}
|
authstateprovider.NotifyAuthenticationChanged();
|
||||||
else
|
NavigationManager.NavigateTo(NavigateUrl(returnurl, true));
|
||||||
{
|
}
|
||||||
if ((PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && PageState.Site.Settings["LoginOptions:TwoFactor"] == "required") || user.TwoFactorRequired)
|
else
|
||||||
{
|
{
|
||||||
twofactor = true;
|
// post back to the Login page so that the cookies are set correctly
|
||||||
validated = false;
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(returnurl) };
|
||||||
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
|
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
||||||
}
|
await interop.SubmitForm(url, fields);
|
||||||
else
|
}
|
||||||
{
|
}
|
||||||
if (!twofactor)
|
else
|
||||||
{
|
{
|
||||||
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
|
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
|
||||||
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
|
{
|
||||||
}
|
twofactor = true;
|
||||||
else
|
validated = false;
|
||||||
{
|
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
|
||||||
await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username);
|
}
|
||||||
AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
|
else
|
||||||
}
|
{
|
||||||
}
|
if (!twofactor)
|
||||||
}
|
{
|
||||||
}
|
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
|
||||||
else
|
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
|
||||||
{
|
}
|
||||||
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
|
else
|
||||||
}
|
{
|
||||||
}
|
await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username);
|
||||||
catch (Exception ex)
|
AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
|
||||||
{
|
}
|
||||||
await logger.LogError(ex, "Error Performing Login {Error}", ex.Message);
|
}
|
||||||
AddModuleMessage(Localizer["Error.Login"], MessageType.Error);
|
}
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Performing Login {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Login"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Cancel()
|
private void Cancel()
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(_returnUrl);
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Forgot()
|
private async Task Forgot()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_username != string.Empty)
|
if (_username != string.Empty)
|
||||||
{
|
{
|
||||||
var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
|
var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await UserService.ForgotPasswordAsync(user);
|
await UserService.ForgotPasswordAsync(user);
|
||||||
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
|
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
|
||||||
AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
|
AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
|
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Resetting Password {Error}", ex.Message);
|
await logger.LogError(ex, "Error Resetting Password {Error}", ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.ResetPassword"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.ResetPassword"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Reset()
|
private void Reset()
|
||||||
{
|
{
|
||||||
twofactor = false;
|
twofactor = false;
|
||||||
_username = "";
|
_username = "";
|
||||||
_password = "";
|
_password = "";
|
||||||
ClearModuleMessage();
|
ClearModuleMessage();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task KeyPressed(KeyboardEventArgs e)
|
private async Task KeyPressed(KeyboardEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Code == "Enter" || e.Code == "NumpadEnter")
|
if (e.Code == "Enter" || e.Code == "NumpadEnter")
|
||||||
{
|
{
|
||||||
await Login();
|
await Login();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TogglePassword()
|
private void TogglePassword()
|
||||||
{
|
{
|
||||||
if (_passwordtype == "password")
|
if (_passwordtype == "password")
|
||||||
{
|
{
|
||||||
_passwordtype = "text";
|
_passwordtype = "text";
|
||||||
_togglepassword = SharedLocalizer["HidePassword"];
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_passwordtype = "password";
|
_passwordtype = "password";
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExternalLogin()
|
private void ExternalLogin()
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + _returnUrl), true);
|
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="category" HelpText="The categories that were affected" ResourceKey="Category">Category: </Label>
|
<Label Class="col-sm-3" For="category" HelpText="The fully qualified type type that was affected" ResourceKey="Category">Type Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="category" class="form-control" @bind="@_category" readonly />
|
<input id="category" class="form-control" @bind="@_category" readonly />
|
||||||
</div>
|
</div>
|
||||||
@@ -130,17 +130,18 @@
|
|||||||
private string _properties = string.Empty;
|
private string _properties = string.Empty;
|
||||||
private string _server = string.Empty;
|
private string _server = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override string UrlParametersTemplate => "/{id}";
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logId = Int32.Parse(PageState.QueryString["id"]);
|
_logId = Int32.Parse(UrlParameters["id"]);
|
||||||
var log = await LogService.GetLogAsync(_logId);
|
var log = await LogService.GetLogAsync(_logId);
|
||||||
if (log != null)
|
if (log != null)
|
||||||
{
|
{
|
||||||
_logDate = log.LogDate.ToString(CultureInfo.CurrentCulture);
|
_logDate = UtcToLocal(log.LogDate).Value.ToString(CultureInfo.CurrentCulture);
|
||||||
_level = log.Level;
|
_level = log.Level;
|
||||||
_feature = log.Feature;
|
_feature = log.Feature;
|
||||||
_function = log.Function;
|
_function = log.Function;
|
||||||
@@ -191,13 +192,6 @@
|
|||||||
|
|
||||||
private string CloseUrl()
|
private string CloseUrl()
|
||||||
{
|
{
|
||||||
if (!PageState.QueryString.ContainsKey("level"))
|
return (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl();
|
||||||
{
|
|
||||||
return NavigateUrl();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return NavigateUrl(PageState.Page.Path, "level=" + PageState.QueryString["level"] + "&function=" + PageState.QueryString["function"] + "&rows=" + PageState.QueryString["rows"] + "&page=" + PageState.QueryString["page"]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Logs
|
@namespace Oqtane.Modules.Admin.Logs
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
@inject ILogService LogService
|
@inject ILogService LogService
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@@ -62,8 +63,8 @@ else
|
|||||||
<th>@Localizer["Function"]</th>
|
<th>@Localizer["Function"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"id=" + context.LogId.ToString() + "&level=" + _level + "&function=" + _function + "&rows=" + _rows + "&page=" + _page.ToString())" ResourceKey="LogDetails" /></td>
|
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Text="Details" Parameters="@($"/{context.LogId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, AddUrlParameters(_level, _function, _rows, _page)))" ResourceKey="LogDetails" /></td>
|
||||||
<td class="@GetClass(context.Function)">@context.LogDate</td>
|
<td class="@GetClass(context.Function)">@UtcToLocal(context.LogDate)</td>
|
||||||
<td class="@GetClass(context.Function)">@context.Level</td>
|
<td class="@GetClass(context.Function)">@context.Level</td>
|
||||||
<td class="@GetClass(context.Function)">@context.Feature</td>
|
<td class="@GetClass(context.Function)">@context.Feature</td>
|
||||||
<td class="@GetClass(context.Function)">@context.Function</td>
|
<td class="@GetClass(context.Function)">@context.Function</td>
|
||||||
@@ -80,12 +81,13 @@ else
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="retention" HelpText="Number of days of events to retain" ResourceKey="Retention">Retention (Days): </Label>
|
<Label Class="col-sm-3" For="retention" HelpText="Number of days of events to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="retention" class="form-control" @bind="@_retention" />
|
<input id="retention" type="number" min="0" step="1" class="form-control" @bind="@_retention" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
|
<ActionDialog Header="Clear Events" Message="Are You Sure You Wish To Remove All Log Events?" Action="DeleteLogs" Class="btn btn-danger" OnClick="@(async () => await DeleteLogs())" ResourceKey="DeleteLogs" />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
}
|
}
|
||||||
@@ -94,37 +96,38 @@ else
|
|||||||
private string _level = "-";
|
private string _level = "-";
|
||||||
private string _function = "-";
|
private string _function = "-";
|
||||||
private string _rows = "10";
|
private string _rows = "10";
|
||||||
private int _page = 1;
|
private int _page = 1;
|
||||||
private List<Log> _logs;
|
private List<Log> _logs;
|
||||||
private string _retention = "";
|
private int _retention = 30;
|
||||||
|
|
||||||
|
public override string UrlParametersTemplate => "/{level}/{function}/{rows}/{page}";
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (PageState.QueryString.ContainsKey("level"))
|
if (UrlParameters.ContainsKey("level"))
|
||||||
{
|
{
|
||||||
_level = PageState.QueryString["level"];
|
_level = UrlParameters["level"];
|
||||||
}
|
}
|
||||||
if (PageState.QueryString.ContainsKey("function"))
|
if (UrlParameters.ContainsKey("function"))
|
||||||
{
|
{
|
||||||
_function = PageState.QueryString["function"];
|
_function = UrlParameters["function"];
|
||||||
}
|
}
|
||||||
if (PageState.QueryString.ContainsKey("rows"))
|
if (UrlParameters.ContainsKey("rows"))
|
||||||
{
|
{
|
||||||
_rows = PageState.QueryString["rows"];
|
_rows = UrlParameters["rows"];
|
||||||
}
|
}
|
||||||
if (PageState.QueryString.ContainsKey("page") && int.TryParse(PageState.QueryString["page"], out int page))
|
if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page))
|
||||||
{
|
{
|
||||||
_page = page;
|
_page = page;
|
||||||
}
|
}
|
||||||
|
|
||||||
await GetLogs();
|
await GetLogs();
|
||||||
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
_retention = SettingService.GetSetting(settings, "LogRetention", "30");
|
_retention = int.Parse( SettingService.GetSetting(settings, "LogRetention", "30"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -211,26 +214,52 @@ else
|
|||||||
return classname;
|
return classname;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveSiteSettings()
|
private async Task SaveSiteSettings()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
settings = SettingService.SetSetting(settings, "LogRetention", _retention, true);
|
settings = SettingService.SetSetting(settings, "LogRetention", _retention.ToString(), true);
|
||||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
|
||||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task DeleteLogs()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LogService.DeleteLogsAsync(PageState.Site.SiteId);
|
||||||
|
await GetLogs();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Deleting Logs {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.DeleteLogs"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnPageChange(int page)
|
private void OnPageChange(int page)
|
||||||
{
|
{
|
||||||
_page = page;
|
_page = page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnAfterRender(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
// external link to log item will display Details component
|
||||||
|
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int id))
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, ModuleState.ModuleId, "Detail", $"/{id}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,203 +0,0 @@
|
|||||||
@namespace Oqtane.Modules.Admin.ModuleCreator
|
|
||||||
@inherits ModuleBase
|
|
||||||
@using System.Text.RegularExpressions
|
|
||||||
@inject NavigationManager NavigationManager
|
|
||||||
@inject IModuleDefinitionService ModuleDefinitionService
|
|
||||||
@inject IModuleService ModuleService
|
|
||||||
@inject ISettingService SettingService
|
|
||||||
@inject IStringLocalizer<Index> Localizer
|
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
|
||||||
|
|
||||||
@if (string.IsNullOrEmpty(_moduledefinitionname) && _templates != 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="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>
|
|
||||||
<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>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="module" class="form-control" @bind="@_module" required />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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" ></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="template" HelpText="Select a module template. Templates are located in the wwwroot/Modules/Templates folder on the server." ResourceKey="Template">Template: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="template" class="form-select" @onchange="(e => TemplateChanged(e))" required>
|
|
||||||
<option value="-"><@Localizer["Template.Select"]></option>
|
|
||||||
@foreach (Template template in _templates)
|
|
||||||
{
|
|
||||||
<option value="@template.Name">@template.Title</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="reference" class="form-select" @bind="@_reference" required>
|
|
||||||
@foreach (string version in _versions)
|
|
||||||
{
|
|
||||||
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
|
|
||||||
{
|
|
||||||
<option value="@(version)">@(version)</option>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
<option value="local">@SharedLocalizer["LocalVersion"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@if (!string.IsNullOrEmpty(_location))
|
|
||||||
{
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="module" class="form-control" @bind="@_location" readonly />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["Module.Create"]</button>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-success" @onclick="ActivateModule">@Localizer["Module.Activate"]</button>
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private ElementReference form;
|
|
||||||
private bool validated = false;
|
|
||||||
private string _moduledefinitionname = string.Empty;
|
|
||||||
private string _owner = string.Empty;
|
|
||||||
private string _module = string.Empty;
|
|
||||||
private string _description = string.Empty;
|
|
||||||
private List<Template> _templates;
|
|
||||||
private string _template = "-";
|
|
||||||
private string[] _versions;
|
|
||||||
private string _reference = Constants.Version;
|
|
||||||
private string _minversion = "2.0.0";
|
|
||||||
private string _location = string.Empty;
|
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
_moduledefinitionname = SettingService.GetSetting(ModuleState.Settings, "ModuleDefinitionName", "");
|
|
||||||
if (string.IsNullOrEmpty(_moduledefinitionname))
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Info.Module.Creator"], MessageType.Info);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Info.Module.Activate"], MessageType.Info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_templates = await ModuleDefinitionService.GetModuleDefinitionTemplatesAsync();
|
|
||||||
_versions = Constants.ReleaseVersions.Split(',').Where(item => Version.Parse(item).CompareTo(Version.Parse("2.0.0")) >= 0).ToArray();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Loading Module Creator");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CreateModule()
|
|
||||||
{
|
|
||||||
validated = true;
|
|
||||||
var interop = new Interop(JSRuntime);
|
|
||||||
if (await interop.FormValid(form))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
|
|
||||||
{
|
|
||||||
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference };
|
|
||||||
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
|
|
||||||
|
|
||||||
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
|
||||||
SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName);
|
|
||||||
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
|
||||||
|
|
||||||
GetLocation();
|
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Require.ValidName"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Creating Module");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ActivateModule()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(_moduledefinitionname))
|
|
||||||
{
|
|
||||||
Module module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
|
|
||||||
module.ModuleDefinitionName = _moduledefinitionname;
|
|
||||||
await ModuleService.UpdateModuleAsync(module);
|
|
||||||
NavigationManager.NavigateTo(NavigateUrl(), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Activating Module");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsValid(string name)
|
|
||||||
{
|
|
||||||
// must contain letters, underscores and digits and first character must be letter or underscore
|
|
||||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TemplateChanged(ChangeEventArgs e)
|
|
||||||
{
|
|
||||||
_template = (string)e.Value;
|
|
||||||
_minversion = "2.0.0";
|
|
||||||
if (_template != "-")
|
|
||||||
{
|
|
||||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
|
||||||
_minversion = template.Version;
|
|
||||||
}
|
|
||||||
GetLocation();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GetLocation()
|
|
||||||
{
|
|
||||||
_location = string.Empty;
|
|
||||||
if (_owner != "" && _module != "" && _template != "-")
|
|
||||||
{
|
|
||||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
|
||||||
_location = template.Location + _owner + "." + _module;
|
|
||||||
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using Oqtane.Documentation;
|
|
||||||
using Oqtane.Models;
|
|
||||||
|
|
||||||
namespace Oqtane.Modules.Admin.ModuleCreator
|
|
||||||
{
|
|
||||||
[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 = "Module Creator",
|
|
||||||
Description = "Enables software developers to quickly create modules by automating many of the initial module creation tasks",
|
|
||||||
Version = "1.0.0",
|
|
||||||
Categories = "Developer"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,69 +8,126 @@
|
|||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Download" ResourceKey="Download">
|
<TabPanel Name="Download" ResourceKey="Download" Heading="Download">
|
||||||
<div class="row justify-content-center mb-3">
|
<div class="row justify-content-center mb-3">
|
||||||
<div class="col-sm-6">
|
<div class="text-center">
|
||||||
<div class="input-group">
|
<div class="form-check form-check-inline">
|
||||||
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
|
<input id="free" class="form-check-input" type="radio" checked="@(_price == "free")" name="Price" @onchange="@(() => PriceChanged("free"))" />
|
||||||
<option value="free">@SharedLocalizer["Free"]</option>
|
<label class="form-check-label" for="free">@SharedLocalizer["Free"]</label>
|
||||||
<option value="paid">@SharedLocalizer["Paid"]</option>
|
</div>
|
||||||
</select>
|
<div class="form-check form-check-inline">
|
||||||
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
|
<input id="paid" class="form-check-input" type="radio" checked="@(_price == "paid")" name="Price" @onchange="@(() => PriceChanged("paid"))" />
|
||||||
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
|
<label class="form-check-label" for="paid">@SharedLocalizer["Paid"]</label>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row justify-content-center mb-3">
|
||||||
@if (_packages != null)
|
<div class="col">
|
||||||
{
|
<div class="input-group">
|
||||||
if (_packages.Count > 0)
|
<span class="input-group-text">@Localizer["Product"]</span>
|
||||||
{
|
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
|
||||||
<Pager Items="@_packages">
|
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
|
||||||
<Row>
|
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
|
||||||
<td>
|
<button type="button" class="btn btn-primary ms-2" @onclick="Refresh"><span class="@Icons.Reload" aria-hidden="true"></span></button>
|
||||||
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3> by: <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
|
|
||||||
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
|
||||||
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"] |
|
|
||||||
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong> |
|
|
||||||
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
|
|
||||||
@((MarkupString)(context.TrialPeriod > 0 ? " | <strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
|
|
||||||
</td>
|
|
||||||
<td style="width: 1px; vertical-align: middle;">
|
|
||||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
<td style="width: 1px; vertical-align: middle;">
|
|
||||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
|
|
||||||
{
|
|
||||||
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</Row>
|
|
||||||
</Pager>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<br />
|
|
||||||
<div class="mx-auto text-center">
|
|
||||||
@Localizer["Search.NoResults"]
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
}
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col">
|
||||||
|
@if (_initialized)
|
||||||
|
{
|
||||||
|
<br />
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<h3>@((_packages != null) ? _packages.Count : 0) @SharedLocalizer["Search.Results"]</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<select class="form-select" value="@_sort" @onchange="(e => SortChanged(e))">
|
||||||
|
<option value="popularity">@SharedLocalizer["Search.Popularity"]</option>
|
||||||
|
<option value="alphabetical">@SharedLocalizer["Search.Alphabetical"]</option>
|
||||||
|
@if (_price == "free")
|
||||||
|
{
|
||||||
|
<option value="downloads">@SharedLocalizer["Search.Downloads"]</option>
|
||||||
|
}
|
||||||
|
<option value="recent">@SharedLocalizer["Search.RecentlyReleased"]</option>
|
||||||
|
@if (_price == "paid")
|
||||||
|
{
|
||||||
|
<option value="price">@SharedLocalizer["Search.Price"]</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Pager Format="Grid" Items="@_packages" DisplayPages="1" PageSize="9" Toolbar="Both" Class="container-fluid px-0" RowClass="row g-0" ColumnClass="col-lg-4 col-md-6" CurrentPage="@_page.ToString()" OnPageChange="OnPageChange">
|
||||||
|
<Row>
|
||||||
|
<div class="m-2 p-2 d-flex justify-content-center">
|
||||||
|
<div class="container-fluid px-0">
|
||||||
|
<div class="row g-0 mb-2">
|
||||||
|
<div class="col-4">
|
||||||
|
<a href="@context.ProductUrl" target="_blank">
|
||||||
|
@if (context.LogoUrl != null)
|
||||||
|
{
|
||||||
|
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<img src="/package.png" class="img-fluid" alt="@context.Name" />
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 text-end">
|
||||||
|
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
|
||||||
|
<br /><small>@SharedLocalizer["Search.Released"]:</small> <strong>@context.ReleaseDate.ToString("MM/dd/yyyy")</strong>
|
||||||
|
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||||
|
{
|
||||||
|
<br /><small>@SharedLocalizer["Search.Source"]:</small> <strong>@(new Uri(context.PackageUrl).Host)</strong>
|
||||||
|
}
|
||||||
|
@if (context.Price == null)
|
||||||
|
{
|
||||||
|
<br /><small>@SharedLocalizer["Search.Downloads"]:</small> <strong>@(String.Format("{0:n0}", context.Downloads))</strong>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<br /><small>@SharedLocalizer["From"]:</small> <strong>@context.Price.Value.ToString("$#,##0.00")</strong>
|
||||||
|
@((MarkupString)(context.TrialPeriod > 0 ? " <strong>(" + context.TrialPeriod + " Day Trial)</strong>" : ""))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row g-0">
|
||||||
|
<div class="col">
|
||||||
|
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_blank">@context.Name</a></h3><br />
|
||||||
|
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
|
||||||
|
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
||||||
|
<br />
|
||||||
|
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||||
|
}
|
||||||
|
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
|
||||||
|
{
|
||||||
|
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
||||||
|
}
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Upload" ResourceKey="Upload">
|
<TabPanel Name="Upload" ResourceKey="Upload" Heading="Upload">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" HelpText="Upload one or more module packages. Once they are uploaded click Install to complete the installation." ResourceKey="Module">Module: </Label>
|
<Label Class="col-sm-3" HelpText="Upload one or more module packages." ResourceKey="Module">Module: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
|
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,9 +147,9 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p style="height: 200px; overflow-y: scroll;">
|
<p style="height: 200px; overflow-y: scroll;">
|
||||||
<h3>@_productname</h3>
|
<h3>@_productname</h3>
|
||||||
@if (!string.IsNullOrEmpty(_license))
|
@if (!string.IsNullOrEmpty(_packagelicense))
|
||||||
{
|
{
|
||||||
@((MarkupString)_license)
|
@((MarkupString)_packagelicense)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -110,17 +167,19 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<button type="button" class="btn btn-success" @onclick="InstallModules">@SharedLocalizer["Install"]</button>
|
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private bool _initialized = false;
|
||||||
|
private int _page = 1;
|
||||||
private List<Package> _packages;
|
private List<Package> _packages;
|
||||||
private string _price = "free";
|
private string _price = "free";
|
||||||
|
private string _sort = "popularity";
|
||||||
private string _search = "";
|
private string _search = "";
|
||||||
private string _productname = "";
|
private string _productname = "";
|
||||||
private string _license = "";
|
|
||||||
private string _packageid = "";
|
private string _packageid = "";
|
||||||
private string _version = "";
|
private string _packagelicense = "";
|
||||||
|
private string _packageversion = "";
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
@@ -129,6 +188,7 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LoadModuleDefinitions();
|
await LoadModuleDefinitions();
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -139,8 +199,10 @@
|
|||||||
|
|
||||||
private async Task LoadModuleDefinitions()
|
private async Task LoadModuleDefinitions()
|
||||||
{
|
{
|
||||||
|
ShowProgressIndicator();
|
||||||
|
|
||||||
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
||||||
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "");
|
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "", _sort);
|
||||||
|
|
||||||
if (_packages != null)
|
if (_packages != null)
|
||||||
{
|
{
|
||||||
@@ -152,52 +214,50 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void PriceChanged(ChangeEventArgs e)
|
private async void PriceChanged(string price)
|
||||||
{
|
{
|
||||||
try
|
_price = price;
|
||||||
{
|
_sort = "popularity";
|
||||||
_price = (string)e.Value;
|
await LoadModuleDefinitions();
|
||||||
_search = "";
|
StateHasChanged();
|
||||||
await LoadModuleDefinitions();
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error On PriceChanged");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Search()
|
private async Task Search()
|
||||||
{
|
{
|
||||||
try
|
await LoadModuleDefinitions();
|
||||||
{
|
|
||||||
await LoadModuleDefinitions();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error On Search");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Reset()
|
private async Task Reset()
|
||||||
{
|
{
|
||||||
try
|
_page = 1;
|
||||||
{
|
_search = "";
|
||||||
_search = "";
|
await LoadModuleDefinitions();
|
||||||
await LoadModuleDefinitions();
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
private async Task Refresh()
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error On Reset");
|
await LoadModuleDefinitions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnPageChange(int page)
|
||||||
|
{
|
||||||
|
_page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void SortChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_sort = (string)e.Value;
|
||||||
|
await LoadModuleDefinitions();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideModal()
|
private void HideModal()
|
||||||
{
|
{
|
||||||
_productname = "";
|
_productname = "";
|
||||||
_license = "";
|
_packagelicense = "";
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,18 +265,23 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var package = await PackageService.GetPackageAsync(packageid, version);
|
var package = await PackageService.GetPackageAsync(packageid, version, false);
|
||||||
if (package != null)
|
if (package != null)
|
||||||
{
|
{
|
||||||
_productname = package.Name;
|
_productname = package.Name;
|
||||||
|
_packageid = package.PackageId;
|
||||||
if (!string.IsNullOrEmpty(package.License))
|
if (!string.IsNullOrEmpty(package.License))
|
||||||
{
|
{
|
||||||
_license = package.License.Replace("\n", "<br />");
|
_packagelicense = package.License.Replace("\n", "<br />");
|
||||||
}
|
}
|
||||||
_packageid = package.PackageId;
|
_packageversion = package.Version;
|
||||||
_version = package.Version;
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await logger.LogError("Error Getting Package {PackageId} {Version}", packageid, version);
|
||||||
|
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
|
||||||
}
|
}
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -229,30 +294,22 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
|
await PackageService.DownloadPackageAsync(_packageid, _packageversion);
|
||||||
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
|
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _packageversion);
|
||||||
AddModuleMessage(Localizer["Success.Module.Download"], MessageType.Success);
|
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
_productname = "";
|
_productname = "";
|
||||||
_license = "";
|
_packagelicense = "";
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _version);
|
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _packageversion);
|
||||||
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task InstallModules()
|
private void OnUpload()
|
||||||
{
|
{
|
||||||
try
|
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
{
|
|
||||||
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
|
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Installing Module");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,13 @@
|
|||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="owner" HelpText="Enter the name of the organization who is developing this module. It should not contain spaces or punctuation." ResourceKey="OwnerName">Owner Name: </Label>
|
<Label Class="col-sm-3" For="owner" HelpText="Enter the name of the organization who is developing this module. It should not contain spaces or punctuation or the word oqtane." ResourceKey="OwnerName">Owner Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="owner" class="form-control" @bind="@_owner" required />
|
<input id="owner" class="form-control" @bind="@_owner" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="module" HelpText="Enter a name for this module. It should not contain spaces or punctuation." ResourceKey="ModuleName">Module Name: </Label>
|
<Label Class="col-sm-3" For="module" HelpText="Enter a name for this module. It should not contain spaces or punctuation or the word oqtane." ResourceKey="ModuleName">Module Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="module" class="form-control" @bind="@_module" required />
|
<input id="module" class="form-control" @bind="@_module" required />
|
||||||
</div>
|
</div>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label>
|
<Label Class="col-sm-3" For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="2000" required></textarea>
|
<textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="2000"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -89,7 +89,10 @@
|
|||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
|
if (!NavigationManager.BaseUri.Contains("localhost:"))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
@@ -108,6 +111,8 @@
|
|||||||
private async Task CreateModule()
|
private async Task CreateModule()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
|
_owner = _owner.Trim();
|
||||||
|
_module = _module.Trim();
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
@@ -115,10 +120,19 @@
|
|||||||
{
|
{
|
||||||
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
|
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
|
||||||
{
|
{
|
||||||
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference };
|
if (string.IsNullOrEmpty(_description)) _description = _module;
|
||||||
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
|
if (IsValidXML(_description))
|
||||||
GetLocation();
|
{
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||||
|
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference, ModuleDefinitionName = template.Namespace };
|
||||||
|
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
|
||||||
|
GetLocation();
|
||||||
|
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Require.ValidDescription"], MessageType.Warning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -139,7 +153,13 @@
|
|||||||
private bool IsValid(string name)
|
private bool IsValid(string name)
|
||||||
{
|
{
|
||||||
// must contain letters, underscores and digits and first character must be letter or underscore
|
// must contain letters, underscores and digits and first character must be letter or underscore
|
||||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
|
return !string.IsNullOrEmpty(name) && name.ToLower() != "module" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsValidXML(string description)
|
||||||
|
{
|
||||||
|
// must contain letters, digits, or spaces
|
||||||
|
return Regex.IsMatch(description, "^[A-Za-z0-9 .,!?]+$");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TemplateChanged(ChangeEventArgs e)
|
private void TemplateChanged(ChangeEventArgs e)
|
||||||
@@ -160,8 +180,14 @@
|
|||||||
if (_owner != "" && _module != "" && _template != "-")
|
if (_owner != "" && _module != "" && _template != "-")
|
||||||
{
|
{
|
||||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||||
_location = template.Location + _owner + "." + _module;
|
if (!string.IsNullOrEmpty(template.Namespace))
|
||||||
|
{
|
||||||
|
_location = template.Location + template.Namespace.Replace("[Owner]", _owner).Replace("[Module]", _module);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_location = template.Location + _owner + ".Module." + _module;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,119 +1,251 @@
|
|||||||
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
|
@using System.Globalization
|
||||||
@inject IModuleDefinitionService ModuleDefinitionService
|
@inject IModuleDefinitionService ModuleDefinitionService
|
||||||
|
@inject IPackageService PackageService
|
||||||
|
@inject ILanguageService LanguageService
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
@inject IPageModuleService PageModuleService
|
||||||
|
@inject IModuleService ModuleService
|
||||||
|
@inject IPageService PageService
|
||||||
|
|
||||||
<TabStrip>
|
@if (_initialized)
|
||||||
<TabPanel Name="Definition" ResourceKey="Definition">
|
{
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<TabStrip>
|
||||||
<div class="container">
|
<TabPanel Name="Definition" ResourceKey="Definition" Heading="Definition">
|
||||||
<div class="row mb-1 align-items-center">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
|
<div class="container">
|
||||||
<div class="col-sm-9">
|
<div class="row mb-1 align-items-center">
|
||||||
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
|
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="description" HelpText="The description of the module" ResourceKey="Description">Description: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="description" class="form-control" @bind="@_description" rows="2" maxlength="2000" required></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="isenabled" HelpText="Is module enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
</form>
|
||||||
<Label Class="col-sm-3" For="description" HelpText="The description of the module" ResourceKey="Description">Description: </Label>
|
<Section Name="Information" ResourceKey="Information">
|
||||||
<div class="col-sm-9">
|
<div class="container">
|
||||||
<textarea id="description" class="form-control" @bind="@_description" rows="2" maxlength="2000" required></textarea>
|
<div class="row mb-1 align-items-center">
|
||||||
</div>
|
<Label Class="col-sm-3" For="moduledefinitionname" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
||||||
</div>
|
<div class="col-sm-9">
|
||||||
<div class="row mb-1 align-items-center">
|
<input id="moduledefinitionname" class="form-control" @bind="@_moduledefinitionname" disabled />
|
||||||
<Label Class="col-sm-3" For="categories" HelpText="Comma delimited list of module categories" ResourceKey="Categories">Categories: </Label>
|
</div>
|
||||||
<div class="col-sm-9">
|
</div>
|
||||||
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" required />
|
<div class="row mb-1 align-items-center">
|
||||||
</div>
|
<Label Class="col-sm-3" For="version" HelpText="The version of the module" ResourceKey="Version">Version: </Label>
|
||||||
</div>
|
<div class="col-sm-9">
|
||||||
</div>
|
<input id="version" class="form-control" @bind="@_version" disabled />
|
||||||
</form>
|
</div>
|
||||||
<Section Name="Information" ResourceKey="Information">
|
</div>
|
||||||
<div class="container">
|
<div class="row mb-1 align-items-center">
|
||||||
<div class="row mb-1 align-items-center">
|
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed. This value must be specified within the module's IModule interface specification." ResourceKey="PackageName">Package Name: </Label>
|
||||||
<Label Class="col-sm-3" For="moduledefinitionname" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
<div class="col-sm-9">
|
||||||
<div class="col-sm-9">
|
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||||
<input id="moduledefinitionname" class="form-control" @bind="@_moduledefinitionname" disabled />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row mb-1 align-items-center">
|
||||||
<div class="row mb-1 align-items-center">
|
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the module" ResourceKey="Owner">Owner: </Label>
|
||||||
<Label Class="col-sm-3" For="version" HelpText="The version of the module" ResourceKey="Version">Version: </Label>
|
<div class="col-sm-9">
|
||||||
<div class="col-sm-9">
|
<input id="owner" class="form-control" @bind="@_owner" disabled />
|
||||||
<input id="version" class="form-control" @bind="@_version" disabled />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row mb-1 align-items-center">
|
||||||
<div class="row mb-1 align-items-center">
|
<Label Class="col-sm-3" For="url" HelpText="The url of the module" ResourceKey="Url">Url: </Label>
|
||||||
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the module" ResourceKey="Owner">Owner: </Label>
|
<div class="col-sm-9">
|
||||||
<div class="col-sm-9">
|
<input id="url" class="form-control" @bind="@_url" disabled />
|
||||||
<input id="owner" class="form-control" @bind="@_owner" disabled />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row mb-1 align-items-center">
|
||||||
<div class="row mb-1 align-items-center">
|
<Label Class="col-sm-3" For="contact" HelpText="The contact for the module" ResourceKey="Contact">Contact: </Label>
|
||||||
<Label Class="col-sm-3" For="url" HelpText="The reference url of the module" ResourceKey="ReferenceUrl">Reference Url: </Label>
|
<div class="col-sm-9">
|
||||||
<div class="col-sm-9">
|
<input id="contact" class="form-control" @bind="@_contact" disabled />
|
||||||
<input id="url" class="form-control" @bind="@_url" disabled />
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row mb-1 align-items-center">
|
||||||
<div class="row mb-1 align-items-center">
|
<Label Class="col-sm-3" For="license" HelpText="The module license terms" ResourceKey="License">License: </Label>
|
||||||
<Label Class="col-sm-3" For="contact" HelpText="The contact for the module" ResourceKey="Contact">Contact: </Label>
|
<div class="col-sm-9">
|
||||||
<div class="col-sm-9">
|
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
|
||||||
<input id="contact" class="form-control" @bind="@_contact" disabled />
|
{
|
||||||
</div>
|
<a href="@_license.Replace("~", PageState?.Alias.BaseUrl + "/Modules/" + Utilities.GetTypeName(_moduledefinitionname))" class="btn btn-info" style="text-decoration: none !important" target="_new">@Localizer["View License"]</a>
|
||||||
</div>
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
else
|
||||||
<Label Class="col-sm-3" For="license" HelpText="The module license terms" ResourceKey="License">License: </Label>
|
{
|
||||||
<div class="col-sm-9">
|
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
|
||||||
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
</div>
|
||||||
<Label Class="col-sm-3" For="runtimes" HelpText="The Blazor runtimes which this module supports" ResourceKey="Runtimes">Runtimes: </Label>
|
</Section>
|
||||||
<div class="col-sm-9">
|
<br />
|
||||||
<input id="runtimes" class="form-control" @bind="@_runtimes" disabled />
|
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
|
||||||
</div>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
</div>
|
<br />
|
||||||
</div>
|
<br />
|
||||||
</Section>
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Permissions" ResourceKey="Permissions">
|
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" Permissions="@_permissions" @ref="_permissionGrid" />
|
<PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
<br />
|
||||||
</TabStrip>
|
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
</TabPanel>
|
||||||
<br />
|
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
|
||||||
<br />
|
<Pager Items="@_pagesWithModules" RowClass="align-middle">
|
||||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => NavigationManager.NavigateTo(Browse(context)))">@Localizer["Browse"]</button></td>
|
||||||
|
<td>@(string.IsNullOrEmpty(context.Title) ? @context.Name : @context.Title )</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Translations" ResourceKey="Translations" Heading="Translations">
|
||||||
|
@if (_languages != null && _languages.Count > 0)
|
||||||
|
{
|
||||||
|
<Pager Items="@_languages">
|
||||||
|
<Header>
|
||||||
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
|
<th>@Localizer["Code"]</th>
|
||||||
|
<th style="width: 1px;">@Localizer["Version"]</th>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td>@context.Name</td>
|
||||||
|
<td>@context.Code</td>
|
||||||
|
<td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</td>
|
||||||
|
<td>
|
||||||
|
@switch (TranslationAvailable(_packagename + "." + context.Code, context.Version))
|
||||||
|
{
|
||||||
|
case "install":
|
||||||
|
<button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(_packagename + "." + context.Code))>@SharedLocalizer["Download"]</button>
|
||||||
|
break;
|
||||||
|
case "upgrade":
|
||||||
|
<button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(_packagename + "." + context.Code))>@SharedLocalizer["Upgrade"]</button>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<br />
|
||||||
|
<div class="mx-auto text-center">
|
||||||
|
@if (string.IsNullOrEmpty(_packagename))
|
||||||
|
{
|
||||||
|
@Localizer["Search.PackageNameMissing"]
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@Localizer["Search.NoResults"]
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
|
|
||||||
|
@if (_package != null)
|
||||||
|
{
|
||||||
|
<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">@SharedLocalizer["Review License Terms"]</h5>
|
||||||
|
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p style="height: 200px; overflow-y: scroll;">
|
||||||
|
<h4 style="display: inline;"><a href="@_package.ProductUrl" target="_new">@_package.Name</a></h4><br />
|
||||||
|
@SharedLocalizer["Search.By"]: <strong><a href="@_package.OwnerUrl" target="new">@_package.Owner</a></strong><br />
|
||||||
|
@(_package.Description.Length > 400 ? (_package.Description.Substring(0, 400) + "...") : _package.Description)<br />
|
||||||
|
<strong>@(String.Format("{0:n0}", _package.Downloads))</strong> @SharedLocalizer["Search.Downloads"] |
|
||||||
|
@SharedLocalizer["Search.Released"]: <strong>@_package.ReleaseDate.ToString("MMM dd, yyyy")</strong> |
|
||||||
|
@SharedLocalizer["Search.Version"]: <strong>@_package.Version</strong>
|
||||||
|
@((MarkupString)(!string.IsNullOrEmpty(_package.PackageUrl) ? " | " + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(_package.PackageUrl).Host + "</strong>" : ""))
|
||||||
|
<br /><br />
|
||||||
|
@if (!string.IsNullOrEmpty(_package.License))
|
||||||
|
{
|
||||||
|
@((MarkupString)_package.License.Replace("\n", "<br />"))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@SharedLocalizer["License Not Specified"]
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-success" @onclick="DownloadTranslation">@SharedLocalizer["Accept"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private bool _initialized = false;
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
private int _moduleDefinitionId;
|
private int _moduleDefinitionId;
|
||||||
private string _name;
|
private string _name;
|
||||||
private string _version;
|
|
||||||
private string _categories;
|
|
||||||
private string _moduledefinitionname = "";
|
|
||||||
private string _description = "";
|
private string _description = "";
|
||||||
|
private string _categories;
|
||||||
|
private string _isenabled;
|
||||||
|
private string _moduledefinitionname = "";
|
||||||
|
private string _version;
|
||||||
|
private string _packagename = "";
|
||||||
private string _owner = "";
|
private string _owner = "";
|
||||||
private string _url = "";
|
private string _url = "";
|
||||||
private string _contact = "";
|
private string _contact = "";
|
||||||
private string _license = "";
|
private string _license = "";
|
||||||
private string _runtimes = "";
|
private List<Permission> _permissions = null;
|
||||||
private string _permissions;
|
|
||||||
private string _createdby;
|
private string _createdby;
|
||||||
private DateTime _createdon;
|
private DateTime _createdon;
|
||||||
private string _modifiedby;
|
private string _modifiedby;
|
||||||
private DateTime _modifiedon;
|
private DateTime _modifiedon;
|
||||||
|
private List<Page> _pagesWithModules;
|
||||||
|
|
||||||
#pragma warning disable 649
|
#pragma warning disable 649
|
||||||
private PermissionGrid _permissionGrid;
|
private PermissionGrid _permissionGrid;
|
||||||
#pragma warning restore 649
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
private List<Package> _packages;
|
||||||
|
private List<Language> _languages;
|
||||||
|
private Package _package;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
@@ -125,20 +257,51 @@
|
|||||||
if (moduleDefinition != null)
|
if (moduleDefinition != null)
|
||||||
{
|
{
|
||||||
_name = moduleDefinition.Name;
|
_name = moduleDefinition.Name;
|
||||||
_version = moduleDefinition.Version;
|
|
||||||
_categories = moduleDefinition.Categories;
|
|
||||||
_moduledefinitionname = moduleDefinition.ModuleDefinitionName;
|
|
||||||
_description = moduleDefinition.Description;
|
_description = moduleDefinition.Description;
|
||||||
|
_categories = moduleDefinition.Categories;
|
||||||
|
_isenabled = moduleDefinition.IsEnabled.ToString();
|
||||||
|
_moduledefinitionname = moduleDefinition.ModuleDefinitionName;
|
||||||
|
_version = moduleDefinition.Version;
|
||||||
|
_packagename = moduleDefinition.PackageName;
|
||||||
_owner = moduleDefinition.Owner;
|
_owner = moduleDefinition.Owner;
|
||||||
_url = moduleDefinition.Url;
|
_url = moduleDefinition.Url;
|
||||||
_contact = moduleDefinition.Contact;
|
_contact = moduleDefinition.Contact;
|
||||||
_license = moduleDefinition.License;
|
_license = moduleDefinition.License;
|
||||||
_runtimes = moduleDefinition.Runtimes;
|
_permissions = moduleDefinition.PermissionList;
|
||||||
_permissions = moduleDefinition.Permissions;
|
|
||||||
_createdby = moduleDefinition.CreatedBy;
|
_createdby = moduleDefinition.CreatedBy;
|
||||||
_createdon = moduleDefinition.CreatedOn;
|
_createdon = moduleDefinition.CreatedOn;
|
||||||
_modifiedby = moduleDefinition.ModifiedBy;
|
_modifiedby = moduleDefinition.ModifiedBy;
|
||||||
_modifiedon = moduleDefinition.ModifiedOn;
|
_modifiedon = moduleDefinition.ModifiedOn;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_packagename))
|
||||||
|
{
|
||||||
|
_packages = await PackageService.GetPackagesAsync("translation", "", "", _packagename);
|
||||||
|
_languages = await LanguageService.GetLanguagesAsync(-1, _packagename);
|
||||||
|
foreach (var package in _packages)
|
||||||
|
{
|
||||||
|
var code = package.PackageId.Split('.').Last();
|
||||||
|
if (!_languages.Any(item => item.Code == code))
|
||||||
|
{
|
||||||
|
_languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = package.Version, IsDefault = true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_languages = _languages.OrderBy(item => item.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get distinct pages where module exists
|
||||||
|
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||||
|
var distinctPageIds = modules
|
||||||
|
.Where(md => md.ModuleDefinition?.ModuleDefinitionId == _moduleDefinitionId && md.IsDeleted == false)
|
||||||
|
.Select(md => md.PageId)
|
||||||
|
.Distinct();
|
||||||
|
|
||||||
|
// retrieve the pages which contain the module
|
||||||
|
var pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
|
_pagesWithModules = pages
|
||||||
|
.Where(pg => distinctPageIds.Contains(pg.PageId) && pg.IsDeleted == false)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -156,23 +319,32 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
|
var moduleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
||||||
if (moduledefinition.Name != _name)
|
if (!moduleDefinitions.Any(item => item.Name.ToLower() == _name.ToLower() && item.ModuleDefinitionId != _moduleDefinitionId))
|
||||||
{
|
{
|
||||||
moduledefinition.Name = _name;
|
var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
|
||||||
|
if (moduledefinition.Name != _name)
|
||||||
|
{
|
||||||
|
moduledefinition.Name = _name;
|
||||||
|
}
|
||||||
|
if (moduledefinition.Description != _description)
|
||||||
|
{
|
||||||
|
moduledefinition.Description = _description;
|
||||||
|
}
|
||||||
|
if (moduledefinition.Categories != _categories)
|
||||||
|
{
|
||||||
|
moduledefinition.Categories = _categories;
|
||||||
|
}
|
||||||
|
moduledefinition.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
|
||||||
|
moduledefinition.PermissionList = _permissionGrid.GetPermissionList();
|
||||||
|
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
|
||||||
|
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
if (moduledefinition.Description != _description)
|
else
|
||||||
{
|
{
|
||||||
moduledefinition.Description = _description;
|
AddModuleMessage(Localizer["Message.DuplicateName"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
if (moduledefinition.Categories != _categories)
|
|
||||||
{
|
|
||||||
moduledefinition.Categories = _categories;
|
|
||||||
}
|
|
||||||
moduledefinition.Permissions = _permissionGrid.GetPermissions();
|
|
||||||
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
|
|
||||||
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);
|
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -185,4 +357,75 @@
|
|||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HideModal()
|
||||||
|
{
|
||||||
|
_package = null;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string TranslationAvailable(string packagename, string version)
|
||||||
|
{
|
||||||
|
if (_packages != null)
|
||||||
|
{
|
||||||
|
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
|
||||||
|
if (package != null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(version))
|
||||||
|
{
|
||||||
|
return "install";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
|
||||||
|
{
|
||||||
|
return "upgrade";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetPackage(string packagename)
|
||||||
|
{
|
||||||
|
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_package = await PackageService.GetPackageAsync(packagename, version, false);
|
||||||
|
if (_package != null)
|
||||||
|
{
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await logger.LogError("Error Getting Package {PackageId} {Version}", packagename, version);
|
||||||
|
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packagename, version);
|
||||||
|
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DownloadTranslation()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version);
|
||||||
|
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _package.PackageId, _package.Version);
|
||||||
|
AddModuleMessage(string.Format(Localizer["Success.Translation.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
|
_package = null;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
|
||||||
|
AddModuleMessage(Localizer["Error.Translation.Download"], 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
|
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IModuleService ModuleService
|
||||||
@inject IModuleDefinitionService ModuleDefinitionService
|
@inject IModuleDefinitionService ModuleDefinitionService
|
||||||
@inject IPackageService PackageService
|
@inject IPackageService PackageService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@@ -12,9 +13,30 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
<div class="container">
|
||||||
@((MarkupString)" ")
|
<div class="row mb-3 align-items-center">
|
||||||
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary" />
|
<div class="col-sm-6">
|
||||||
|
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
||||||
|
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ps-2" />
|
||||||
|
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<select class="form-select" @onchange="(e => CategoryChanged(e))">
|
||||||
|
@foreach (var category in _categories)
|
||||||
|
{
|
||||||
|
if (category == _category)
|
||||||
|
{
|
||||||
|
<option value="@category" selected>@category @Localizer["Modules"]</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<option value="@category">@category @Localizer["Modules"]</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Pager Items="@_moduleDefinitions">
|
<Pager Items="@_moduleDefinitions">
|
||||||
<Header>
|
<Header>
|
||||||
@@ -22,22 +44,24 @@ else
|
|||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
<th>@SharedLocalizer["Version"]</th>
|
<th>@SharedLocalizer["Version"]</th>
|
||||||
|
<th>@Localizer["Enabled"]</th>
|
||||||
<th>@Localizer["InUse"]</th>
|
<th>@Localizer["InUse"]</th>
|
||||||
|
<th>@SharedLocalizer["Support"]</th>
|
||||||
<th>@SharedLocalizer["Expires"]</th>
|
<th>@SharedLocalizer["Expires"]</th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.ModuleDefinitionId.ToString())" ResourceKey="EditModule" /></td>
|
||||||
<td>
|
<td>
|
||||||
@if (context.AssemblyName != "Oqtane.Client")
|
@if (context.AssemblyName != Constants.ClientId)
|
||||||
{
|
{
|
||||||
<ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete", context.Name])" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" />
|
<ActionDialog Header="Delete Module" Message="@string.Format(Localizer["Confirm.Module.Delete", context.Name])" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" />
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
<td>@context.Version</td>
|
<td>@context.Version</td>
|
||||||
<td>
|
<td>
|
||||||
@if(context.AssemblyName == "Oqtane.Client" || PageState.Modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
|
@if (context.IsEnabled)
|
||||||
{
|
{
|
||||||
<span>@SharedLocalizer["Yes"]</span>
|
<span>@SharedLocalizer["Yes"]</span>
|
||||||
}
|
}
|
||||||
@@ -46,13 +70,26 @@ else
|
|||||||
<span>@SharedLocalizer["No"]</span>
|
<span>@SharedLocalizer["No"]</span>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
@if (context.AssemblyName == Constants.ClientId || _modules.Where(m => m.ModuleDefinition?.ModuleDefinitionId == context.ModuleDefinitionId).FirstOrDefault() != null)
|
||||||
|
{
|
||||||
|
<span>@SharedLocalizer["Yes"]</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>@SharedLocalizer["No"]</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@((MarkupString)SupportLink(context.PackageName, context.Version))
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@((MarkupString)PurchaseLink(context.PackageName))
|
@((MarkupString)PurchaseLink(context.PackageName))
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@{
|
@{
|
||||||
var version = UpgradeAvailable(context.PackageName, context.Version);
|
var version = UpgradeAvailable(context.PackageName, context.Version);
|
||||||
}
|
}
|
||||||
@if (version != context.Version)
|
@if (version != context.Version)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadModule(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
|
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadModule(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
|
||||||
@@ -63,58 +100,87 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<ModuleDefinition> _moduleDefinitions;
|
private List<Module> _modules;
|
||||||
private List<Package> _packages;
|
private List<ModuleDefinition> _allModuleDefinitions;
|
||||||
|
private List<ModuleDefinition> _moduleDefinitions;
|
||||||
|
private List<Package> _packages;
|
||||||
|
private List<string> _categories = new List<string>();
|
||||||
|
private string _category = "Common";
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_moduleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||||
_packages = await PackageService.GetPackagesAsync("module");
|
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
||||||
}
|
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',')).Distinct().ToList();
|
||||||
catch (Exception ex)
|
await LoadModuleDefinitions();
|
||||||
{
|
}
|
||||||
if (_moduleDefinitions == null)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Loading Modules {Error}", ex.Message);
|
if (_moduleDefinitions == null)
|
||||||
AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error);
|
{
|
||||||
}
|
await logger.LogError(ex, "Error Loading Modules {Error}", ex.Message);
|
||||||
}
|
AddModuleMessage(Localizer["Error.Module.Load"], MessageType.Error);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string PurchaseLink(string packagename)
|
private async Task LoadModuleDefinitions()
|
||||||
{
|
{
|
||||||
string link = "";
|
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
|
||||||
if (!string.IsNullOrEmpty(packagename) && _packages != null)
|
_packages = await PackageService.GetPackageUpdatesAsync("module");
|
||||||
{
|
}
|
||||||
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
|
|
||||||
if (package != null)
|
|
||||||
{
|
|
||||||
if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date)
|
|
||||||
{
|
|
||||||
link += "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span>";
|
|
||||||
if (!string.IsNullOrEmpty(package.PaymentUrl))
|
|
||||||
{
|
|
||||||
link += " <a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + SharedLocalizer["Extend"] + "</a>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string UpgradeAvailable(string packagename, string version)
|
private string PurchaseLink(string packagename)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(packagename) && _packages != null)
|
string link = "";
|
||||||
{
|
if (!string.IsNullOrEmpty(packagename) && _packages != null)
|
||||||
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
|
{
|
||||||
if (package != null && Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
|
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
|
||||||
{
|
if (package != null)
|
||||||
return package.Version;
|
{
|
||||||
}
|
if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(package.PaymentUrl))
|
||||||
|
{
|
||||||
|
link = "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span>";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
link = "<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</a>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string SupportLink(string packagename, string version)
|
||||||
|
{
|
||||||
|
string link = "";
|
||||||
|
if (!string.IsNullOrEmpty(packagename) && _packages != null)
|
||||||
|
{
|
||||||
|
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
|
||||||
|
if (package != null && !string.IsNullOrEmpty(package.SupportUrl))
|
||||||
|
{
|
||||||
|
link += "<a class=\"btn btn-info\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl.Replace("{Version}", version) + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string UpgradeAvailable(string packagename, string version)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(packagename) && _packages != null)
|
||||||
|
{
|
||||||
|
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
|
||||||
|
if (package != null && Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
|
||||||
|
{
|
||||||
|
return package.Version;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
@@ -123,9 +189,8 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
|
await PackageService.DownloadPackageAsync(packagename, version);
|
||||||
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
|
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
|
||||||
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
|
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
|
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -149,4 +214,33 @@ else
|
|||||||
AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Module.Delete"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CategoryChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_category = (string)e.Value;
|
||||||
|
await LoadModuleDefinitions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Synchronize()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ShowProgressIndicator();
|
||||||
|
foreach (var moduleDefinition in _moduleDefinitions)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(moduleDefinition.PackageName) && !_packages.Any(item => item.PackageId == moduleDefinition.PackageName))
|
||||||
|
{
|
||||||
|
var package = await PackageService.GetPackageAsync(moduleDefinition.PackageName, moduleDefinition.Version, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HideProgressIndicator();
|
||||||
|
AddModuleMessage(Localizer["Success.Module.Synchronize"], MessageType.Success);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Synchronizing Modules {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Module.Synchronize"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,29 +5,61 @@
|
|||||||
@inject IStringLocalizer<Export> Localizer
|
@inject IStringLocalizer<Export> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<div class="container">
|
<TabStrip>
|
||||||
<div class="row mb-1 align-items-center">
|
<TabPanel Name="Content" Heading="Content" ResourceKey="Content">
|
||||||
<Label Class="col-sm-3" For="content" HelpText="The Exported Module Content" ResourceKey="Content">Content: </Label>
|
<div class="container">
|
||||||
<div class="col-sm-9">
|
<div class="row mb-1 align-items-center">
|
||||||
<textarea id="content" class="form-control" @bind="@_content" rows="5" readonly></textarea>
|
<Label Class="col-sm-3" For="content" HelpText="Select the Export option and you will be able to view the module content" ResourceKey="Content">Content: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="content" class="form-control" @bind="@_content" rows="5" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<br />
|
||||||
</div>
|
<button type="button" class="btn btn-success" @onclick="ExportText">@Localizer["Export"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="File" Heading="File" ResourceKey="File">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="folder" HelpText="Select a folder where you wish to save the exported content" ResourceKey="Folder">Folder: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<FileManager ShowFiles="false" ShowUpload="false" @ref="_filemanager" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="filename" HelpText="Specify a name for the file (without an extension)" ResourceKey="Filename">Filename: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="content" type="text" class="form-control" @bind="@_filename" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="ExportFile">@Localizer["Export"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
|
|
||||||
|
|
||||||
<button type="button" class="btn btn-success" @onclick="ExportModule">@Localizer["Export"]</button>
|
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _content = string.Empty;
|
private string _content = string.Empty;
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
private FileManager _filemanager;
|
||||||
|
private string _filename = string.Empty;
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
public override string Title => "Export Content";
|
public override string Title => "Export Content";
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_filename = Utilities.GetFriendlyUrl(ModuleState.Title);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ExportModule()
|
private async Task ExportText()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId);
|
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId);
|
||||||
AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -36,4 +68,34 @@
|
|||||||
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ExportFile()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var folderid = _filemanager.GetFolderId();
|
||||||
|
if (folderid != -1 && !string.IsNullOrEmpty(_filename))
|
||||||
|
{
|
||||||
|
var fileid = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, folderid, _filename);
|
||||||
|
if (fileid != -1)
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Content.Export"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Exporting Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,29 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IModuleService ModuleService
|
@inject IModuleService ModuleService
|
||||||
|
@inject IFileService FileService
|
||||||
@inject IStringLocalizer<Import> Localizer
|
@inject IStringLocalizer<Import> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="content" HelpText="Enter The Module Content To Import" ResourceKey="Content">Content: </Label>
|
<Label Class="col-sm-3" For="file" HelpText="Optionally upload or select a file to import for this module" ResourceKey="File">File: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<FileManager Filter="json" OnSelectFile="OnSelectFile" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="content" HelpText="Provide the module content to import" ResourceKey="Content">Content: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="content" class="form-control" @bind="@_content" rows="5" required></textarea>
|
<textarea id="content" class="form-control" @bind="@_content" rows="5" required></textarea>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
|
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -25,9 +32,15 @@
|
|||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
public override string Title => "Import Content";
|
public override string Title => "Import Content";
|
||||||
|
|
||||||
|
private async Task OnSelectFile(int fileId)
|
||||||
|
{
|
||||||
|
var bytes = await FileService.DownloadFileAsync(fileId);
|
||||||
|
_content = System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ImportModule()
|
private async Task ImportModule()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
@@ -38,7 +51,7 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
bool success = await ModuleService.ImportModuleAsync(ModuleState.ModuleId, _content);
|
bool success = await ModuleService.ImportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId, _content);
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Success.Content.Import"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.Content.Import"], MessageType.Success);
|
||||||
|
|||||||
@@ -3,110 +3,173 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IThemeService ThemeService
|
@inject IThemeService ThemeService
|
||||||
|
@inject IPageService PageService
|
||||||
@inject IModuleService ModuleService
|
@inject IModuleService ModuleService
|
||||||
@inject IPageModuleService PageModuleService
|
@inject IPageModuleService PageModuleService
|
||||||
@inject IStringLocalizer<Settings> Localizer
|
@inject IStringLocalizer<Settings> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
@if (_initialized)
|
||||||
<TabStrip>
|
{
|
||||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
@if (_containers != null)
|
<TabStrip ActiveTab="@_activetab">
|
||||||
{
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||||
<div class="container">
|
@if (_containers != null)
|
||||||
<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="container">
|
||||||
<div class="col-sm-9">
|
<div class="row mb-1 align-items-center">
|
||||||
<input id="title" type="text" name="Title" class="form-control" @bind="@_title" required />
|
<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>
|
<div class="row mb-1 align-items-center">
|
||||||
<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>
|
||||||
<Label Class="col-sm-3" For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
|
<div class="col-sm-9">
|
||||||
<div class="col-sm-9">
|
<input id="title" type="text" class="form-control" @bind="@_title" required />
|
||||||
<select id="container" class="form-select" @bind="@_containerType" required>
|
</div>
|
||||||
@foreach (var container in _containers)
|
|
||||||
{
|
|
||||||
<option value="@container.TypeName">@container.Name</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row mb-1 align-items-center">
|
||||||
<div class="row mb-1 align-items-center">
|
<Label Class="col-sm-3" For="pane" HelpText="The pane where the module will be displayed" ResourceKey="Pane">Pane: </Label>
|
||||||
<Label Class="col-sm-3" For="allpages" HelpText="Indicate if this module should be displayed on all pages" ResourceKey="DisplayOnAllPages">Display On All Pages? </Label>
|
<div class="col-sm-9">
|
||||||
<div class="col-sm-9">
|
<select class="form-select" @bind="@_pane">
|
||||||
<select id="allpages" class="form-select" @bind="@_allPages" required>
|
@foreach (string pane in PageState.Page.Panes)
|
||||||
<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>
|
|
||||||
@foreach (Page p in PageState.Pages)
|
|
||||||
{
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
|
|
||||||
{
|
{
|
||||||
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
|
<option value="@pane">@pane Pane</option>
|
||||||
}
|
}
|
||||||
}
|
</select>
|
||||||
</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)
|
||||||
|
{
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<br />
|
||||||
}
|
<Section Name="ModuleContent" Heading="Content" ResourceKey="ModuleContent">
|
||||||
</TabPanel>
|
<div class="container">
|
||||||
<TabPanel Name="Permissions" ResourceKey="Permissions">
|
<div class="row mb-1 align-items-center">
|
||||||
@if (_permissions != null)
|
<Label Class="col-sm-3" For="moduleheader" HelpText="Optionally provide content to be injected above the module instance" ResourceKey="Header">Header: </Label>
|
||||||
{
|
<div class="col-sm-9">
|
||||||
<div class="container">
|
<textarea id="moduleheader" class="form-control" @bind="@_header" rows="3" maxlength="4000"></textarea>
|
||||||
<div class="row mb-1 align-items-center">
|
</div>
|
||||||
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" Permissions="@_permissions" @ref="_permissionGrid" />
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="modulefooter" HelpText="Optionally provide content to be injected below the module instance" ResourceKey="Footer">Footer: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="modulefooter" class="form-control" @bind="@_footer" rows="3" maxlength="4000"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
}
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel 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>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
}
|
||||||
|
</TabPanel>
|
||||||
|
@if (_moduleSettingsType != null)
|
||||||
|
{
|
||||||
|
<TabPanel Name="ModuleSettings" Heading="@_moduleSettingsTitle" ResourceKey="ModuleSettings">
|
||||||
|
@ModuleSettingsComponent
|
||||||
|
</TabPanel>
|
||||||
}
|
}
|
||||||
</TabPanel>
|
@if (_containerSettingsType != null)
|
||||||
@if (_moduleSettingsType != null)
|
{
|
||||||
{
|
<TabPanel Name="ContainerSettings" Heading="Container Settings" ResourceKey="ContainerSettings">
|
||||||
<TabPanel Name="ModuleSettings" Heading="@_moduleSettingsTitle" ResourceKey="ModuleSettings">
|
@ContainerSettingsComponent
|
||||||
@ModuleSettingsComponent
|
</TabPanel>
|
||||||
</TabPanel>
|
}
|
||||||
}
|
</TabStrip>
|
||||||
@if (_containerSettingsType != null)
|
<br />
|
||||||
{
|
<button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button>
|
||||||
<TabPanel Name="ContainerSettings" Heading="Container Settings" ResourceKey="ContainerSettings">
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
@ContainerSettingsComponent
|
<br />
|
||||||
</TabPanel>
|
<br />
|
||||||
}
|
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
||||||
</TabStrip>
|
</form>
|
||||||
<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>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
public override string Title => "Module Settings";
|
|
||||||
|
|
||||||
|
private bool _initialized = false;
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
private List<Theme> _themes;
|
|
||||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||||
|
private string _module;
|
||||||
private string _title;
|
private string _title;
|
||||||
|
private string _pane;
|
||||||
private string _containerType;
|
private string _containerType;
|
||||||
private string _allPages = "false";
|
private string _allPages = "false";
|
||||||
|
private string _header = "";
|
||||||
|
private string _footer = "";
|
||||||
private string _permissionNames = "";
|
private string _permissionNames = "";
|
||||||
private string _permissions = null;
|
private List<Permission> _permissions = null;
|
||||||
private string _pageId;
|
private string _pageId;
|
||||||
private PermissionGrid _permissionGrid;
|
private PermissionGrid _permissionGrid;
|
||||||
private Type _moduleSettingsType;
|
private Type _moduleSettingsType;
|
||||||
private object _moduleSettings;
|
private object _moduleSettings;
|
||||||
private string _moduleSettingsTitle = "Module Settings";
|
private string _moduleSettingsTitle;
|
||||||
private RenderFragment ModuleSettingsComponent { get; set; }
|
private RenderFragment ModuleSettingsComponent { get; set; }
|
||||||
private Type _containerSettingsType;
|
private Type _containerSettingsType;
|
||||||
private object _containerSettings;
|
private object _containerSettings;
|
||||||
@@ -115,49 +178,79 @@
|
|||||||
private DateTime createdon;
|
private DateTime createdon;
|
||||||
private string modifiedby;
|
private string modifiedby;
|
||||||
private DateTime modifiedon;
|
private DateTime modifiedon;
|
||||||
|
private DateTime? _effectivedate = null;
|
||||||
|
private DateTime? _expirydate = null;
|
||||||
|
private List<Page> _pages;
|
||||||
|
private string _activetab = "";
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_title = ModuleState.Title;
|
SetModuleTitle(Localizer["ModuleSettings.Title"]);
|
||||||
_themes = await ThemeService.GetThemesAsync();
|
_moduleSettingsTitle = Localizer["ModuleSettings.Heading"];
|
||||||
_containers = ThemeService.GetContainerControls(_themes, PageState.Page.ThemeType);
|
|
||||||
_containerType = ModuleState.ContainerType;
|
|
||||||
_allPages = ModuleState.AllPages.ToString();
|
|
||||||
_permissions = ModuleState.Permissions;
|
|
||||||
_permissionNames = ModuleState.ModuleDefinition.PermissionNames;
|
|
||||||
_pageId = ModuleState.PageId.ToString();
|
|
||||||
createdby = ModuleState.CreatedBy;
|
|
||||||
createdon = ModuleState.CreatedOn;
|
|
||||||
modifiedby = ModuleState.ModifiedBy;
|
|
||||||
modifiedon = ModuleState.ModifiedOn;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
|
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
|
||||||
|
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
|
|
||||||
|
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
||||||
|
|
||||||
|
_pageId = pagemodule.PageId.ToString();
|
||||||
|
_title = pagemodule.Title;
|
||||||
|
_pane = pagemodule.Pane;
|
||||||
|
_containerType = pagemodule.ContainerType;
|
||||||
|
if (string.IsNullOrEmpty(_containerType))
|
||||||
{
|
{
|
||||||
// module settings type explicitly declared in IModule interface
|
_containerType = (!string.IsNullOrEmpty(PageState.Page.DefaultContainerType)) ? PageState.Page.DefaultContainerType : PageState.Site.DefaultContainerType;
|
||||||
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType);
|
}
|
||||||
|
_header = pagemodule.Header;
|
||||||
|
_footer = pagemodule.Footer;
|
||||||
|
_effectivedate = Utilities.UtcAsLocalDate(pagemodule.EffectiveDate);
|
||||||
|
_expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate);
|
||||||
|
|
||||||
|
_allPages = pagemodule.Module.AllPages.ToString();
|
||||||
|
createdby = pagemodule.Module.CreatedBy;
|
||||||
|
createdon = pagemodule.Module.CreatedOn;
|
||||||
|
modifiedby = pagemodule.Module.ModifiedBy;
|
||||||
|
modifiedon = pagemodule.Module.ModifiedOn;
|
||||||
|
_permissions = pagemodule.Module.PermissionList;
|
||||||
|
|
||||||
|
if (pagemodule.Module.ModuleDefinition != null)
|
||||||
|
{
|
||||||
|
_module = pagemodule.Module.ModuleDefinition.Name;
|
||||||
|
_permissionNames = pagemodule.Module.ModuleDefinition?.PermissionNames;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(pagemodule.Module.ModuleDefinition.SettingsType))
|
||||||
|
{
|
||||||
|
// module settings type explicitly declared in IModule interface
|
||||||
|
_moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.SettingsType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
|
||||||
|
_moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
|
||||||
|
}
|
||||||
|
if (_moduleSettingsType != null)
|
||||||
|
{
|
||||||
|
var moduleobject = Activator.CreateInstance(_moduleSettingsType) as IModuleControl;
|
||||||
|
if (!string.IsNullOrEmpty(moduleobject.Title))
|
||||||
|
{
|
||||||
|
_moduleSettingsTitle = moduleobject.Title;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModuleSettingsComponent = builder =>
|
||||||
|
{
|
||||||
|
builder.OpenComponent(0, _moduleSettingsType);
|
||||||
|
builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
|
||||||
|
builder.AddComponentReferenceCapture(2, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
|
||||||
|
builder.CloseComponent();
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
|
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], pagemodule.Module.ModuleDefinitionName), MessageType.Error);
|
||||||
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
|
|
||||||
}
|
|
||||||
if (_moduleSettingsType != null)
|
|
||||||
{
|
|
||||||
var moduleobject = Activator.CreateInstance(_moduleSettingsType) as IModuleControl;
|
|
||||||
if (!string.IsNullOrEmpty(moduleobject.Title))
|
|
||||||
{
|
|
||||||
_moduleSettingsTitle = moduleobject.Title;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModuleSettingsComponent = builder =>
|
|
||||||
{
|
|
||||||
builder.OpenComponent(0, _moduleSettingsType);
|
|
||||||
builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
|
|
||||||
builder.CloseComponent();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var theme = _themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
|
var theme = PageState.Site.Themes.FirstOrDefault(item => item.Containers.Any(themecontrol => themecontrol.TypeName.Equals(_containerType)));
|
||||||
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
|
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
|
||||||
{
|
{
|
||||||
_containerSettingsType = Type.GetType(theme.ContainerSettingsType);
|
_containerSettingsType = Type.GetType(theme.ContainerSettingsType);
|
||||||
@@ -166,24 +259,37 @@
|
|||||||
ContainerSettingsComponent = builder =>
|
ContainerSettingsComponent = builder =>
|
||||||
{
|
{
|
||||||
builder.OpenComponent(0, _containerSettingsType);
|
builder.OpenComponent(0, _containerSettingsType);
|
||||||
builder.AddComponentReferenceCapture(1, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); });
|
builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
|
||||||
|
builder.AddComponentReferenceCapture(2, inst => { _containerSettings = Convert.ChangeType(inst, _containerSettingsType); });
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveModule()
|
private async Task SaveModule()
|
||||||
{
|
{
|
||||||
|
|
||||||
validated = true;
|
validated = true;
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_title))
|
if (!string.IsNullOrEmpty(_title))
|
||||||
{
|
{
|
||||||
|
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
||||||
pagemodule.PageId = int.Parse(_pageId);
|
pagemodule.PageId = int.Parse(_pageId);
|
||||||
pagemodule.Title = _title;
|
pagemodule.Title = _title;
|
||||||
|
pagemodule.Pane = _pane;
|
||||||
|
pagemodule.EffectiveDate = Utilities.LocalDateAndTimeAsUtc(_effectivedate);
|
||||||
|
pagemodule.ExpiryDate = Utilities.LocalDateAndTimeAsUtc(_expirydate);
|
||||||
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
|
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
|
||||||
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
|
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
|
||||||
{
|
{
|
||||||
@@ -193,12 +299,15 @@
|
|||||||
{
|
{
|
||||||
pagemodule.ContainerType = string.Empty;
|
pagemodule.ContainerType = string.Empty;
|
||||||
}
|
}
|
||||||
|
pagemodule.Header = _header;
|
||||||
|
pagemodule.Footer = _footer;
|
||||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
|
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
|
||||||
|
|
||||||
var module = ModuleState;
|
var module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
|
||||||
module.AllPages = bool.Parse(_allPages);
|
module.AllPages = bool.Parse(_allPages);
|
||||||
module.Permissions = _permissionGrid.GetPermissions();
|
module.PageModuleId = ModuleState.PageModuleId;
|
||||||
|
module.PermissionList = _permissionGrid.GetPermissionList();
|
||||||
await ModuleService.UpdateModuleAsync(module);
|
await ModuleService.UpdateModuleAsync(module);
|
||||||
|
|
||||||
if (_moduleSettingsType != null)
|
if (_moduleSettingsType != null)
|
||||||
@@ -220,17 +329,18 @@
|
|||||||
await containerSettingsControl.UpdateSettings();
|
await containerSettingsControl.UpdateSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
_activetab = "Settings";
|
||||||
AddModuleMessage(Localizer["Message.Required.Title"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Required.Title"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
_activetab = "Settings";
|
||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,58 +3,83 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IPageService PageService
|
@inject IPageService PageService
|
||||||
@inject IThemeService ThemeService
|
@inject IThemeService ThemeService
|
||||||
|
@inject ISystemService SystemService
|
||||||
@inject IStringLocalizer<Add> Localizer
|
@inject IStringLocalizer<Add> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
@if (_initialized)
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
{
|
||||||
<TabStrip Refresh="@_refresh">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<TabPanel Name="Settings" ResourceKey="Settings">
|
<TabStrip Refresh="@_refresh">
|
||||||
@if (_themeList != null)
|
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
|
||||||
{
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="name" class="form-control" @bind="@_name" required />
|
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
|
{
|
||||||
<div class="col-sm-9">
|
<div class="row mb-1 align-items-center">
|
||||||
<select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required>
|
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
|
||||||
<option value="-1"><@Localizer["SiteRoot"]></option>
|
<div class="col-sm-9">
|
||||||
@foreach (Page page in _pageList)
|
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
|
||||||
{
|
<option value="-1"><@Localizer["SiteRoot"]></option>
|
||||||
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
|
@foreach (Page page in _pages)
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="insert" class="form-select" @bind="@_insert" required>
|
|
||||||
<option value="<<">@Localizer["AtBeginning"]</option>
|
|
||||||
@if (_children != null && _children.Count > 0)
|
|
||||||
{
|
|
||||||
<option value="<">@Localizer["Before"]</option>
|
|
||||||
<option value=">">@Localizer["After"]</option>
|
|
||||||
}
|
|
||||||
<option value=">>">@Localizer["AtEnd"]</option>
|
|
||||||
</select>
|
|
||||||
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
|
|
||||||
{
|
|
||||||
<select class="form-select" @bind="@_childid">
|
|
||||||
<option value="-1"><@Localizer["Page.Select"]></option>
|
|
||||||
@foreach (Page page in _children)
|
|
||||||
{
|
{
|
||||||
<option value="@(page.PageId)">@(page.Name)</option>
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList))
|
||||||
|
{
|
||||||
|
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="insert" class="form-select" @bind="@_insert" required>
|
||||||
|
@if (_children != null && _children.Count > 0)
|
||||||
|
{
|
||||||
|
<option value="<<">@Localizer["AtBeginning"]</option>
|
||||||
|
<option value="<">@Localizer["Before"]</option>
|
||||||
|
<option value=">">@Localizer["After"]</option>
|
||||||
|
}
|
||||||
|
<option value=">>">@Localizer["AtEnd"]</option>
|
||||||
|
</select>
|
||||||
|
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
|
||||||
|
{
|
||||||
|
<select class="form-select" @bind="@_childid">
|
||||||
|
<option value="-1"><@Localizer["Page.Select"]></option>
|
||||||
|
@foreach (Page page in _children)
|
||||||
|
{
|
||||||
|
<option value="@(page.PageId)">@(page.Name)</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required>
|
||||||
|
<option value="@(_parent.PageId)">@(new string('-', _parent.Level * 2))@(_parent.Name)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="insert" class="form-select" @bind="@_insert" required>
|
||||||
|
<option value=">>">@Localizer["AtEnd"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -76,29 +101,53 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
<Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="path" class="form-control" @bind="@_path" />
|
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
|
<Label Class="col-sm-3" For="url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="url" class="form-control" @bind="@_url" />
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1">
|
||||||
|
<i class="@_icon"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="personalizable" class="form-select" @bind="@_ispersonalizable" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Section Name="Appearance" ResourceKey="Appearance">
|
<Section Name="Appearance" ResourceKey="Appearance" Heading=@Localizer["Appearance.Name"]>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="title" class="form-control" @bind="@_title" />
|
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="meta" HelpText="Optionally enter meta tags (in exactly the form you want them to be included in the page output)." ResourceKey="Meta">Meta: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<textarea id="meta" class="form-control" @bind="@_meta" rows="3"></textarea>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -107,7 +156,14 @@
|
|||||||
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
||||||
@foreach (var theme in _themes)
|
@foreach (var theme in _themes)
|
||||||
{
|
{
|
||||||
<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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,7 +172,6 @@
|
|||||||
<Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
|
<Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="container" class="form-select" @bind="@_containertype" required>
|
<select id="container" class="form-select" @bind="@_containertype" required>
|
||||||
<option value="-"><@Localizer["Container.Select"]></option>
|
|
||||||
@foreach (var container in _containers)
|
@foreach (var container in _containers)
|
||||||
{
|
{
|
||||||
<option value="@container.TypeName">@container.Name</option>
|
<option value="@container.TypeName">@container.Name</option>
|
||||||
@@ -124,277 +179,301 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
<Section Name="PageContent" ResourceKey="PageContent" Heading=@Localizer["PageContent.Heading"]>
|
||||||
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="icon" class="form-control" @bind="@_icon" />
|
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3" maxlength="4000"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="personalizable" class="form-select" @bind="@_ispersonalizable" required>
|
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3" maxlength="4000"></textarea>
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
}
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel Name="Permissions" ResourceKey="Permissions">
|
|
||||||
<div class="container">
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TabPanel>
|
|
||||||
@if (_themeSettingsType != null)
|
|
||||||
{
|
|
||||||
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
|
|
||||||
@ThemeSettingsComponent
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
}
|
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading=@Localizer["Permissions.Heading"]>
|
||||||
</TabStrip>
|
<div class="container">
|
||||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
<div class="row mb-1 align-items-center">
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
|
||||||
</form>
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
|
|
||||||
private List<Theme> _themeList;
|
private bool _initialized = false;
|
||||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
private ElementReference form;
|
||||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
private bool validated = false;
|
||||||
private List<Page> _pageList;
|
private List<ThemeControl> _themes = new List<ThemeControl>();
|
||||||
private string _name;
|
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||||
private string _title;
|
private List<Page> _pages;
|
||||||
private string _meta;
|
private int _pageId;
|
||||||
private string _path = string.Empty;
|
private string _name;
|
||||||
private string _parentid = "-1";
|
private string _parentid = "-1";
|
||||||
private string _insert = ">>";
|
private string _insert = ">>";
|
||||||
private List<Page> _children;
|
private List<Page> _children;
|
||||||
private int _childid = -1;
|
private int _childid = -1;
|
||||||
private string _isnavigation = "True";
|
private string _isnavigation = "True";
|
||||||
private string _isclickable = "True";
|
private string _isclickable = "True";
|
||||||
private string _url;
|
private string _path = string.Empty;
|
||||||
private string _ispersonalizable = "False";
|
private string _url;
|
||||||
private string _themetype = string.Empty;
|
private string _ispersonalizable = "False";
|
||||||
private string _containertype = string.Empty;
|
private string _title;
|
||||||
private string _icon = string.Empty;
|
private string _icon = string.Empty;
|
||||||
private string _permissions = string.Empty;
|
private string _themetype = string.Empty;
|
||||||
private PermissionGrid _permissionGrid;
|
private string _containertype = string.Empty;
|
||||||
private Type _themeSettingsType;
|
private string _headcontent;
|
||||||
private object _themeSettings;
|
private string _bodycontent;
|
||||||
private RenderFragment ThemeSettingsComponent { get; set; }
|
private string _permissions = null;
|
||||||
private bool _refresh = false;
|
private PermissionGrid _permissionGrid;
|
||||||
private ElementReference form;
|
private bool _refresh = false;
|
||||||
private bool validated = 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()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_themeList = await ThemeService.GetThemesAsync();
|
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
_themes = ThemeService.GetThemeControls(_themeList);
|
|
||||||
_themetype = PageState.Site.DefaultThemeType;
|
|
||||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
|
||||||
_containertype = PageState.Site.DefaultContainerType;
|
|
||||||
_pageList = PageState.Pages;
|
|
||||||
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
|
|
||||||
_permissions = string.Empty;
|
|
||||||
ThemeSettings();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Initializing Page {Error}", ex.Message);
|
|
||||||
AddModuleMessage(Localizer["Error.Page.Initialize"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void ParentChanged(ChangeEventArgs e)
|
if (PageState.QueryString.ContainsKey("id"))
|
||||||
{
|
{
|
||||||
try
|
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
||||||
{
|
_parent = await PageService.GetPageAsync(_pageId);
|
||||||
_parentid = (string)e.Value;
|
if (_parent != null)
|
||||||
_children = new List<Page>();
|
{
|
||||||
if (_parentid == "-1")
|
_parentid = _parent.PageId.ToString();
|
||||||
{
|
}
|
||||||
foreach (Page p in PageState.Pages.Where(item => item.ParentId == null))
|
}
|
||||||
{
|
_icons = await SystemService.GetIconsAsync();
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
|
_iconresources = typeof(IconResources).FullName;
|
||||||
{
|
|
||||||
_children.Add(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (Page p in PageState.Pages.Where(item => item.ParentId == int.Parse(_parentid)))
|
|
||||||
{
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
|
|
||||||
{
|
|
||||||
_children.Add(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void ThemeChanged(ChangeEventArgs e)
|
// if admin or user has edit access to parent page
|
||||||
{
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || (_parent != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _parent.PermissionList)))
|
||||||
try
|
{
|
||||||
{
|
_themetype = PageState.Site.DefaultThemeType;
|
||||||
_themetype = (string)e.Value;
|
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
||||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||||
_containertype = "-";
|
_containertype = PageState.Site.DefaultContainerType;
|
||||||
ThemeSettings();
|
_children = new List<Page>();
|
||||||
StateHasChanged();
|
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message);
|
_children.Add(p);
|
||||||
AddModuleMessage(Localizer["Error.Pane.Load"], MessageType.Error);
|
}
|
||||||
}
|
}
|
||||||
}
|
_effectivedate = Utilities.UtcAsLocalDate(PageState.Page.EffectiveDate);
|
||||||
|
_expirydate = Utilities.UtcAsLocalDate(PageState.Page.ExpiryDate);
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await logger.LogWarning("Error Loading Page {ParentId}", _parentid);
|
||||||
|
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 void ThemeSettings()
|
private async void ParentChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_themeSettingsType = null;
|
try
|
||||||
var theme = _themeList.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
|
{
|
||||||
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
|
_parentid = (string)e.Value;
|
||||||
{
|
_children = new List<Page>();
|
||||||
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);
|
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||||
if (_themeSettingsType != null)
|
{
|
||||||
{
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||||
ThemeSettingsComponent = builder =>
|
{
|
||||||
{
|
_children.Add(p);
|
||||||
builder.OpenComponent(0, _themeSettingsType);
|
}
|
||||||
builder.AddComponentReferenceCapture(1, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
|
}
|
||||||
builder.CloseComponent();
|
StateHasChanged();
|
||||||
};
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
_refresh = true;
|
{
|
||||||
}
|
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 async Task SavePage()
|
private async Task ThemeChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
validated = true;
|
_themetype = (string)e.Value;
|
||||||
var interop = new Interop(JSRuntime);
|
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||||
if (await interop.FormValid(form))
|
_containertype = _containers.First().TypeName;
|
||||||
{
|
StateHasChanged();
|
||||||
Page page = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
|
|
||||||
{
|
|
||||||
page = new Page();
|
|
||||||
page.SiteId = PageState.Page.SiteId;
|
|
||||||
page.Name = _name;
|
|
||||||
page.Title = _title;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_path))
|
// 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)
|
||||||
_path = _name;
|
{
|
||||||
}
|
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
|
||||||
if (_path.Contains("/"))
|
await ScrollToPageTop();
|
||||||
{
|
}
|
||||||
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (_parentid == "-1")
|
private async Task SavePage()
|
||||||
{
|
{
|
||||||
page.ParentId = null;
|
validated = true;
|
||||||
page.Path = Utilities.GetFriendlyUrl(_path);
|
var interop = new Interop(JSRuntime);
|
||||||
}
|
if (await interop.FormValid(form))
|
||||||
else
|
{
|
||||||
{
|
Page page = null;
|
||||||
page.ParentId = Int32.Parse(_parentid);
|
try
|
||||||
var parent = PageState.Pages.Where(item => item.PageId == page.ParentId).FirstOrDefault();
|
{
|
||||||
if (parent.Path == string.Empty)
|
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||||
{
|
{
|
||||||
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||||
}
|
await ScrollToPageTop();
|
||||||
else
|
return;
|
||||||
{
|
}
|
||||||
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
|
if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype))
|
||||||
}
|
{
|
||||||
}
|
page = new Page();
|
||||||
|
page.SiteId = PageState.Page.SiteId;
|
||||||
|
page.Name = _name;
|
||||||
|
|
||||||
if(PagePathIsDeleted(page.Path, page.SiteId, _pageList))
|
if (_parentid == "-1")
|
||||||
{
|
{
|
||||||
AddModuleMessage(string.Format(Localizer["Message.Page.Deleted"], _path), MessageType.Warning);
|
page.ParentId = null;
|
||||||
return;
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
page.ParentId = Int32.Parse(_parentid);
|
||||||
|
}
|
||||||
|
|
||||||
if (!PagePathIsUnique(page.Path, page.SiteId, _pageList))
|
if (string.IsNullOrEmpty(_path))
|
||||||
{
|
{
|
||||||
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
|
_path = _name;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Page child;
|
if (_path.Contains("/"))
|
||||||
switch (_insert)
|
{
|
||||||
{
|
if (_path.EndsWith("/") && _path != "/")
|
||||||
case "<<":
|
{
|
||||||
page.Order = 0;
|
_path = _path.Substring(0, _path.Length - 1);
|
||||||
break;
|
}
|
||||||
case "<":
|
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
||||||
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
|
}
|
||||||
page.Order = child.Order - 1;
|
if (_parentid == "-1")
|
||||||
break;
|
{
|
||||||
case ">":
|
page.Path = Utilities.GetFriendlyUrl(_path);
|
||||||
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
|
}
|
||||||
page.Order = child.Order + 1;
|
else
|
||||||
break;
|
{
|
||||||
case ">>":
|
Page parent = _pages.FirstOrDefault(item => item.PageId == page.ParentId);
|
||||||
page.Order = int.MaxValue;
|
if (parent.Path == string.Empty)
|
||||||
break;
|
{
|
||||||
}
|
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
|
if (_pages.Any(item => item.Path == page.Path))
|
||||||
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
|
{
|
||||||
page.Url = _url;
|
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
|
||||||
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
|
await ScrollToPageTop();
|
||||||
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
|
return;
|
||||||
{
|
}
|
||||||
page.ThemeType = string.Empty;
|
|
||||||
}
|
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
|
||||||
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
|
{
|
||||||
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
|
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
|
||||||
{
|
await ScrollToPageTop();
|
||||||
page.DefaultContainerType = string.Empty;
|
return;
|
||||||
}
|
}
|
||||||
page.Icon = (_icon == null ? string.Empty : _icon);
|
|
||||||
page.Permissions = _permissionGrid.GetPermissions();
|
Page child;
|
||||||
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
|
switch (_insert)
|
||||||
page.UserId = null;
|
{
|
||||||
page.Meta = _meta;
|
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.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 content
|
||||||
|
page.HeadContent = _headcontent;
|
||||||
|
page.BodyContent = _bodycontent;
|
||||||
|
|
||||||
|
// permissions
|
||||||
|
page.PermissionList = _permissionGrid.GetPermissionList();
|
||||||
|
|
||||||
page = await PageService.AddPageAsync(page);
|
page = await PageService.AddPageAsync(page);
|
||||||
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
|
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
|
||||||
|
|
||||||
await logger.LogInformation("Page Added {Page}", page);
|
await logger.LogInformation("Page Added {Page}", page);
|
||||||
if (PageState.QueryString.ContainsKey("cp"))
|
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
|
NavigationManager.NavigateTo(NavigateUrl(page.Path), true); // redirect to page added and reload
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl(page.Path));
|
NavigationManager.NavigateTo(NavigateUrl()); // redirect to page management
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -402,33 +481,29 @@
|
|||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
|
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
|
||||||
|
await ScrollToPageTop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Cancel()
|
private void Cancel()
|
||||||
{
|
{
|
||||||
if (PageState.QueryString.ContainsKey("cp"))
|
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private void IconChanged(string NewIcon)
|
||||||
private static bool PagePathIsUnique(string pagePath, int siteId, List<Page> existingPages)
|
|
||||||
{
|
{
|
||||||
return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath);
|
_icon = NewIcon;
|
||||||
}
|
|
||||||
|
|
||||||
private static bool PagePathIsDeleted(string pagePath, int siteId, List<Page> existingPages)
|
|
||||||
{
|
|
||||||
return existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath && page.IsDeleted == true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,11 +5,11 @@
|
|||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (PageState.Pages != null)
|
@if (_pages != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" />
|
<ActionLink Action="Add" Text="Add Page" ResourceKey="AddPage" />
|
||||||
|
|
||||||
<Pager Items="@PageState.Pages.Where(item => !item.IsDeleted)">
|
<Pager Items="@_pages.Where(item => !item.IsDeleted)" SearchProperties="Name">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td>
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td>
|
||||||
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
||||||
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => NavigationManager.NavigateTo(Browse(context)))">@Localizer["Browse"]</button></td>
|
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => NavigationManager.NavigateTo(Browse(context)))">@Localizer["Browse"]</button></td>
|
||||||
<td>@(new string('-', context.Level * 2))@(context.Name)</td>
|
<td>@(new string('-', context.Level * 2))@(context.Name)</td>
|
||||||
@@ -28,6 +28,21 @@
|
|||||||
@code {
|
@code {
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
|
private List<Page> _pages;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading Pages {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task DeletePage(Page page)
|
private async Task DeletePage(Page page)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="description" HelpText="The help text displayed to the user for this profile item" ResourceKey="Description">Description: </Label>
|
<Label Class="col-sm-3" For="description" HelpText="The help text displayed to the user for this profile item" ResourceKey="Description">Description: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="description" class="form-control" @bind="@_description" rows="5" maxlength="256" required ></textarea>
|
<textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="256" required></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -34,19 +34,25 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="order" HelpText="The index order of where this profile item should be displayed" ResourceKey="Order">Order: </Label>
|
<Label Class="col-sm-3" For="order" HelpText="The index order of where this profile item should be displayed" ResourceKey="Order">Order: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="order" class="form-control" @bind="@_vieworder" maxlength="4" required />
|
<input id="order" class="form-control" @bind="@_vieworder" min="0" max="9999" type="number" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="length" HelpText="The max number of characters this profile item should accept (enter zero for unlimited)" ResourceKey="Length">Length: </Label>
|
<Label Class="col-sm-3" For="length" HelpText="The max number of characters this profile item should accept (enter zero for unlimited)" ResourceKey="Length">Length: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="length" class="form-control" @bind="@_maxlength" maxlength="4" required />
|
<input id="length" class="form-control" @bind="@_maxlength" min="0" max="524288" type="number" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="rows" HelpText="The number of rows for text entry (one is the default)" ResourceKey="Rows">Rows: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="rows" class="form-control" @bind="@_rows" min="1" max="10" type="number" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="defaultVal" HelpText="The default value for this profile item" ResourceKey="DefaultValue">Default Value: </Label>
|
<Label Class="col-sm-3" For="defaultVal" HelpText="The default value for this profile item" ResourceKey="DefaultValue">Default Value: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="defaultVal" class="form-control" @bind="@_defaultvalue" maxlength="2000"/>
|
<input id="defaultVal" class="form-control" @bind="@_defaultvalue" maxlength="2000" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -64,6 +70,18 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="validation" HelpText="Optionally provide a regular expression (RegExp) for validating the value entered" ResourceKey="Validation">Validation: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<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">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="private" HelpText="Should this profile item be visible to all users?" ResourceKey="Private">Private? </Label>
|
<Label Class="col-sm-3" For="private" HelpText="Should this profile item be visible to all users?" ResourceKey="Private">Private? </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -77,7 +95,7 @@
|
|||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveProfile">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveProfile">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
@if (PageState.QueryString.ContainsKey("id"))
|
@if (PageState.QueryString.ContainsKey("id"))
|
||||||
{
|
{
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
@@ -95,8 +113,11 @@
|
|||||||
private string _category = string.Empty;
|
private string _category = string.Empty;
|
||||||
private string _vieworder = "0";
|
private string _vieworder = "0";
|
||||||
private string _maxlength = "0";
|
private string _maxlength = "0";
|
||||||
|
private string _rows = "1";
|
||||||
private string _defaultvalue = string.Empty;
|
private string _defaultvalue = string.Empty;
|
||||||
private string _options = string.Empty;
|
private string _options = string.Empty;
|
||||||
|
private string _validation = string.Empty;
|
||||||
|
private string _autocomplete = string.Empty;
|
||||||
private string _isrequired = "False";
|
private string _isrequired = "False";
|
||||||
private string _isprivate = "False";
|
private string _isprivate = "False";
|
||||||
private string createdby;
|
private string createdby;
|
||||||
@@ -104,7 +125,7 @@
|
|||||||
private string modifiedby;
|
private string modifiedby;
|
||||||
private DateTime modifiedon;
|
private DateTime modifiedon;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
|
|
||||||
public override string Actions => "Add,Edit";
|
public override string Actions => "Add,Edit";
|
||||||
|
|
||||||
@@ -124,8 +145,11 @@
|
|||||||
_category = profile.Category;
|
_category = profile.Category;
|
||||||
_vieworder = profile.ViewOrder.ToString();
|
_vieworder = profile.ViewOrder.ToString();
|
||||||
_maxlength = profile.MaxLength.ToString();
|
_maxlength = profile.MaxLength.ToString();
|
||||||
|
_rows = profile.Rows.ToString();
|
||||||
_defaultvalue = profile.DefaultValue;
|
_defaultvalue = profile.DefaultValue;
|
||||||
_options = profile.Options;
|
_options = profile.Options;
|
||||||
|
_validation = profile.Validation;
|
||||||
|
_autocomplete = profile.Autocomplete;
|
||||||
_isrequired = profile.IsRequired.ToString();
|
_isrequired = profile.IsRequired.ToString();
|
||||||
_isprivate = profile.IsPrivate.ToString();
|
_isprivate = profile.IsPrivate.ToString();
|
||||||
createdby = profile.CreatedBy;
|
createdby = profile.CreatedBy;
|
||||||
@@ -167,10 +191,14 @@
|
|||||||
profile.Category = _category;
|
profile.Category = _category;
|
||||||
profile.ViewOrder = int.Parse(_vieworder);
|
profile.ViewOrder = int.Parse(_vieworder);
|
||||||
profile.MaxLength = int.Parse(_maxlength);
|
profile.MaxLength = int.Parse(_maxlength);
|
||||||
|
profile.Rows = int.Parse(_rows);
|
||||||
profile.DefaultValue = _defaultvalue;
|
profile.DefaultValue = _defaultvalue;
|
||||||
profile.Options = _options;
|
profile.Options = _options;
|
||||||
|
profile.Validation = _validation;
|
||||||
|
profile.Autocomplete = _autocomplete;
|
||||||
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
|
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
|
||||||
profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate));
|
profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate));
|
||||||
|
|
||||||
if (_profileid != -1)
|
if (_profileid != -1)
|
||||||
{
|
{
|
||||||
profile = await ProfileService.UpdateProfileAsync(profile);
|
profile = await ProfileService.UpdateProfileAsync(profile);
|
||||||
|
|||||||
@@ -10,18 +10,24 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ActionLink Action="Add" Security="SecurityAccessLevel.Admin" Text="Add Profile" ResourceKey="AddProfile" />
|
<ActionLink Action="Add" Text="Add Profile" Security="SecurityAccessLevel.Edit" ResourceKey="AddProfile" />
|
||||||
|
|
||||||
<Pager Items="@_profiles">
|
<Pager Items="@_profiles" SearchProperties="Title,Category">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
|
<th>@Localizer["Title"]</th>
|
||||||
|
<th>@Localizer["Category"]</th>
|
||||||
|
<th>@Localizer["Order"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" 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" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
|
<td><ActionDialog Header="Delete Profile" Message="@string.Format(Localizer["Confirm.Profile.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
|
<td>@context.Title</td>
|
||||||
|
<td>@context.Category</td>
|
||||||
|
<td>@context.ViewOrder</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
}
|
}
|
||||||
@@ -29,7 +35,7 @@ else
|
|||||||
@code {
|
@code {
|
||||||
private List<Profile> _profiles;
|
private List<Profile> _profiles;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
|
|||||||
20
Oqtane.Client/Modules/Admin/Profiles/ModuleInfo.cs
Normal file
20
Oqtane.Client/Modules/Admin/Profiles/ModuleInfo.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using Oqtane.Documentation;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Modules.Admin.Profiles
|
||||||
|
{
|
||||||
|
[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 = "Profiles",
|
||||||
|
Description = "Manage Profiles",
|
||||||
|
Categories = "Admin",
|
||||||
|
Version = Constants.Version,
|
||||||
|
PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit}," +
|
||||||
|
$"{EntityNames.Profile}:{PermissionNames.Write}:{RoleNames.Admin}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,76 +7,79 @@
|
|||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<TabStrip>
|
@if (_pages == null || _modules == null)
|
||||||
<TabPanel Name="Pages" ResourceKey="Pages">
|
{
|
||||||
@if (_pages == null)
|
<p><em>@SharedLocalizer["Loading"]</em></p>
|
||||||
{
|
}
|
||||||
<br />
|
else
|
||||||
<p>@Localizer["NoPage.Deleted"]</p>
|
{
|
||||||
}
|
<TabStrip>
|
||||||
else
|
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
|
||||||
{
|
@if (!_pages.Where(item => item.IsDeleted).Any())
|
||||||
<Pager Items="@_pages">
|
|
||||||
<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>
|
|
||||||
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">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>
|
|
||||||
@if (_pages.Any())
|
|
||||||
{
|
{
|
||||||
<br /><ActionDialog Header="Delete All Pages" Message="Are You Sure You Wish To Permanently Delete All Pages?" Action="Delete All Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
|
<br />
|
||||||
|
<p>@Localizer["NoPage.Deleted"]</p>
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
</TabPanel>
|
|
||||||
<TabPanel Name="Modules" ResourceKey="Modules">
|
|
||||||
@if (_modules == null)
|
|
||||||
{
|
|
||||||
<br />
|
|
||||||
<p>@Localizer["NoModule.Deleted"]</p>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<Pager Items="@_modules">
|
|
||||||
<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>@PageState.Pages.Find(item => item.PageId == context.PageId).Name</td>
|
|
||||||
<td>@context.Title</td>
|
|
||||||
<td>@context.DeletedBy</td>
|
|
||||||
<td>@context.DeletedOn</td>
|
|
||||||
</Row>
|
|
||||||
</Pager>
|
|
||||||
@if (_modules.Any())
|
|
||||||
{
|
{
|
||||||
<br /><ActionDialog Header="Delete All Modules" Message="Are You Sure You Wish To Permanently Delete All Modules?" Action="Delete All Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
|
<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.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">
|
||||||
</TabPanel>
|
@if (!_modules.Where(item => item.IsDeleted).Any())
|
||||||
</TabStrip>
|
{
|
||||||
|
<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 {
|
@code {
|
||||||
private List<Page> _pages;
|
private List<Page> _pages;
|
||||||
private List<Module> _modules;
|
private List<Module> _modules;
|
||||||
|
private int _pagePage = 1;
|
||||||
|
private int _pageModule = 1;
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
@@ -95,22 +98,32 @@
|
|||||||
private async Task Load()
|
private async Task Load()
|
||||||
{
|
{
|
||||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||||
_pages = _pages.Where(item => item.IsDeleted).ToList();
|
|
||||||
|
|
||||||
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||||
_modules = _modules.Where(item => item.IsDeleted).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RestorePage(Page page)
|
private async Task RestorePage(Page page)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
page.IsDeleted = false;
|
var validated = true;
|
||||||
await PageService.UpdatePageAsync(page);
|
if (page.ParentId != null)
|
||||||
await logger.LogInformation("Page Restored {Page}", page);
|
{
|
||||||
await Load();
|
var parent = _pages.Find(item => item.PageId == page.ParentId);
|
||||||
StateHasChanged();
|
validated = !parent.IsDeleted;
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
}
|
||||||
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -125,9 +138,9 @@
|
|||||||
{
|
{
|
||||||
await PageService.DeletePageAsync(page.PageId);
|
await PageService.DeletePageAsync(page.PageId);
|
||||||
await logger.LogInformation("Page Permanently Deleted {Page}", page);
|
await logger.LogInformation("Page Permanently Deleted {Page}", page);
|
||||||
|
AddModuleMessage(Localizer["Success.Page.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -140,21 +153,24 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (Page page in _pages)
|
ShowProgressIndicator();
|
||||||
|
foreach (Page page in _pages.Where(item => item.IsDeleted))
|
||||||
{
|
{
|
||||||
await PageService.DeletePageAsync(page.PageId);
|
await PageService.DeletePageAsync(page.PageId);
|
||||||
await logger.LogInformation("Page Permanently Deleted {Page}", page);
|
await logger.LogInformation("Page Permanently Deleted {Page}", page);
|
||||||
}
|
}
|
||||||
|
|
||||||
await logger.LogInformation("Pages Permanently Deleted");
|
await logger.LogInformation("Pages Permanently Deleted");
|
||||||
|
AddModuleMessage(Localizer["Success.Pages.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
|
HideProgressIndicator();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Permanently Deleting Pages {Error}", ex.Message);
|
await logger.LogError(ex, "Error Permanently Deleting Pages {Error}", ex.Message);
|
||||||
AddModuleMessage(ex.Message, MessageType.Error);
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +182,7 @@
|
|||||||
pagemodule.IsDeleted = false;
|
pagemodule.IsDeleted = false;
|
||||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||||
await logger.LogInformation("Module Restored {Module}", module);
|
await logger.LogInformation("Module Restored {Module}", module);
|
||||||
|
AddModuleMessage(Localizer["Success.Module.Restore"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
@@ -181,15 +198,8 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||||
// check if there are any remaining module instances in the site
|
|
||||||
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
|
||||||
|
|
||||||
if (!_modules.Exists(item => item.ModuleId == module.ModuleId))
|
|
||||||
{
|
|
||||||
await ModuleService.DeleteModuleAsync(module.ModuleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
await logger.LogInformation("Module Permanently Deleted {Module}", module);
|
await logger.LogInformation("Module Permanently Deleted {Module}", module);
|
||||||
|
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
@@ -204,26 +214,30 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (Module module in _modules)
|
ShowProgressIndicator();
|
||||||
|
foreach (Module module in _modules.Where(item => item.IsDeleted).ToList())
|
||||||
{
|
{
|
||||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||||
// check if there are any remaining module instances in the site
|
|
||||||
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
|
||||||
|
|
||||||
if (!_modules.Exists(item => item.ModuleId == module.ModuleId))
|
|
||||||
{
|
|
||||||
await ModuleService.DeleteModuleAsync(module.ModuleId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await logger.LogInformation("Modules Permanently Deleted");
|
await logger.LogInformation("Modules Permanently Deleted");
|
||||||
|
AddModuleMessage(Localizer["Success.Modules.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
|
HideProgressIndicator();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Permanently Deleting Modules {Error}", ex.Message);
|
await logger.LogError(ex, "Error Permanently Deleting Modules {Error}", ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.Modules.Delete"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Modules.Delete"], MessageType.Error);
|
||||||
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private void OnPageChangePage(int page)
|
||||||
|
{
|
||||||
|
_pagePage = page;
|
||||||
|
}
|
||||||
|
private void OnPageChangeModule(int page)
|
||||||
|
{
|
||||||
|
_pageModule = page;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,89 +1,129 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Register
|
@namespace Oqtane.Modules.Admin.Register
|
||||||
|
@using System.Net
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
|
@inject ITimeZoneService TimeZoneService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
|
||||||
@if (PageState.Site.AllowRegistration)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
<AuthorizeView Roles="@RoleNames.Registered">
|
@if (PageState.Site.AllowRegistration)
|
||||||
<Authorizing>
|
{
|
||||||
<text>...</text>
|
if (!_userCreated)
|
||||||
</Authorizing>
|
{
|
||||||
<Authorized>
|
if (PageState.User != null)
|
||||||
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
|
{
|
||||||
</Authorized>
|
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
|
||||||
<NotAuthorized>
|
}
|
||||||
<ModuleMessage Message="@Localizer["Info.Registration.InvalidEmail"]" Type="MessageType.Info" />
|
else
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
{
|
||||||
<div class="container">
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
<div class="row mb-1 align-items-center">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
|
<div class="container">
|
||||||
<div class="col-sm-9">
|
<div class="row mb-1 align-items-center">
|
||||||
<input id="username" class="form-control" @bind="@_username" maxlength="256" required />
|
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="username" class="form-control" @bind="@_username" maxlength="256" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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 />
|
||||||
|
<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="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 />
|
||||||
|
<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="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 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var timezone in _timezones)
|
||||||
|
{
|
||||||
|
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<br />
|
||||||
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
|
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
||||||
<div class="col-sm-9">
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
<div class="input-group">
|
@if (_allowsitelogin)
|
||||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
{
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
<br />
|
||||||
</div>
|
|
||||||
</div>
|
<br />
|
||||||
</div>
|
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
|
||||||
<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>
|
</form>
|
||||||
<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">@_togglepassword</button>
|
else
|
||||||
</div>
|
{
|
||||||
</div>
|
<ModuleMessage Message="@Localizer["Info.Registration.Disabled"]" Type="MessageType.Info" />
|
||||||
</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 />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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>
|
|
||||||
</form>
|
|
||||||
</NotAuthorized>
|
|
||||||
</AuthorizeView>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<ModuleMessage Message="@Localizer["Info.Registration.Disabled"]" Type="MessageType.Info" />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _username = string.Empty;
|
private bool _initialized = false;
|
||||||
private ElementReference form;
|
private List<Models.TimeZone> _timezones;
|
||||||
private bool validated = false;
|
private string _passwordrequirements;
|
||||||
private string _password = string.Empty;
|
private string _username = string.Empty;
|
||||||
private string _passwordtype = "password";
|
private ElementReference form;
|
||||||
private string _togglepassword = string.Empty;
|
private bool validated = false;
|
||||||
private string _confirm = string.Empty;
|
private string _password = string.Empty;
|
||||||
private string _email = string.Empty;
|
private string _passwordtype = "password";
|
||||||
private string _displayname = string.Empty;
|
private string _togglepassword = string.Empty;
|
||||||
|
private string _confirm = string.Empty;
|
||||||
|
private string _email = string.Empty;
|
||||||
|
private string _displayname = string.Empty;
|
||||||
|
private string _timezoneid = string.Empty;
|
||||||
|
private bool _userCreated = false;
|
||||||
|
private bool _allowsitelogin = true;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
}
|
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||||
|
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||||
|
_timezoneid = PageState.Site.TimeZoneId;
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
|
||||||
private async Task Register()
|
private async Task Register()
|
||||||
{
|
{
|
||||||
@@ -106,6 +146,7 @@ else
|
|||||||
Password = _password,
|
Password = _password,
|
||||||
Email = _email,
|
Email = _email,
|
||||||
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
||||||
|
TimeZoneId = _timezoneid,
|
||||||
PhotoFileId = null
|
PhotoFileId = null
|
||||||
};
|
};
|
||||||
user = await UserService.AddUserAsync(user);
|
user = await UserService.AddUserAsync(user);
|
||||||
@@ -113,7 +154,8 @@ else
|
|||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await logger.LogInformation("User Created {Username} {Email}", _username, _email);
|
await logger.LogInformation("User Created {Username} {Email}", _username, _email);
|
||||||
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
|
_userCreated = true;
|
||||||
|
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -145,7 +187,7 @@ else
|
|||||||
|
|
||||||
private void Cancel()
|
private void Cancel()
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TogglePassword()
|
private void TogglePassword()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="username" HelpText="Your username will be populated from the link you received in the password reset notification" ResourceKey="Username">Username: </Label>
|
<Label Class="col-sm-3" For="username" HelpText="Your username will be populated from the link you received in the password reset notification" ResourceKey="Username">Username: </Label>
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,12 +46,14 @@
|
|||||||
private string _passwordtype = "password";
|
private string _passwordtype = "password";
|
||||||
private string _togglepassword = string.Empty;
|
private string _togglepassword = string.Empty;
|
||||||
private string _confirm = string.Empty;
|
private string _confirm = string.Empty;
|
||||||
|
private string _passwordrequirements;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("name") && PageState.QueryString.ContainsKey("token"))
|
if (PageState.QueryString.ContainsKey("name") && PageState.QueryString.ContainsKey("token"))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
private string _description = string.Empty;
|
private string _description = string.Empty;
|
||||||
private string _isautoassigned = "False";
|
private string _isautoassigned = "False";
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
|
|
||||||
private async Task SaveRole()
|
private async Task SaveRole()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
private string _modifiedby;
|
private string _modifiedby;
|
||||||
private DateTime _modifiedon;
|
private DateTime _modifiedon;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Add Role" ResourceKey="AddRole" />
|
<ActionLink Action="Add" Text="Add Role" Security="SecurityAccessLevel.Edit" ResourceKey="AddRole" />
|
||||||
|
|
||||||
<Pager Items="@_roles">
|
<Pager Items="@_roles" SearchProperties="Name">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -20,9 +20,9 @@ else
|
|||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.RoleId.ToString())" 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.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td>
|
<td><ActionDialog Header="Delete Role" Message="@string.Format(Localizer["Confirm.DeleteUser"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td>
|
||||||
<td><ActionLink Action="Users" Parameters="@($"id=" + context.RoleId.ToString())" ResourceKey="Users" /></td>
|
<td><ActionLink Action="Users" Text="Users" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Users" /></td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@@ -31,7 +31,7 @@ else
|
|||||||
@code {
|
@code {
|
||||||
private List<Role> _roles;
|
private List<Role> _roles;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
@@ -59,7 +59,7 @@ else
|
|||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
_roles = await RoleService.GetRolesAsync(PageState.Site.SiteId, true);
|
_roles = await RoleService.GetRolesAsync(PageState.Site.SiteId, true);
|
||||||
_roles = _roles.Where(item => item.Name != RoleNames.Everyone).ToList();
|
_roles.RemoveAll(item => item.Name == RoleNames.Everyone || item.Name == RoleNames.Unauthenticated);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
21
Oqtane.Client/Modules/Admin/Roles/ModuleInfo.cs
Normal file
21
Oqtane.Client/Modules/Admin/Roles/ModuleInfo.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Oqtane.Documentation;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Modules.Admin.Roles
|
||||||
|
{
|
||||||
|
[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 = "Roles",
|
||||||
|
Description = "Manage Roles",
|
||||||
|
Categories = "Admin",
|
||||||
|
Version = Constants.Version,
|
||||||
|
PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit}," +
|
||||||
|
$"{EntityNames.Role}:{PermissionNames.Write}:{RoleNames.Admin}," +
|
||||||
|
$"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,13 +23,7 @@ else
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="user" HelpText="Select a user" ResourceKey="User">User: </Label>
|
<Label Class="col-sm-3" For="user" HelpText="Select a user" ResourceKey="User">User: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="user" class="form-select" @bind="@userid" required>
|
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["User.Select"]" @ref="user" />
|
||||||
<option value="-1"><@Localizer["User.Select"]></option>
|
|
||||||
@foreach (UserRole userrole in users)
|
|
||||||
{
|
|
||||||
<option value="@(userrole.UserId)">@userrole.User.DisplayName</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -64,7 +58,7 @@ else
|
|||||||
<td>@context.EffectiveDate</td>
|
<td>@context.EffectiveDate</td>
|
||||||
<td>@context.ExpiryDate</td>
|
<td>@context.ExpiryDate</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || PageState.User.Username == UserNames.Host)" ResourceKey="DeleteUserRole" />
|
<ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.User.Username == UserNames.Host || context.User.UserId == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
|
||||||
</td>
|
</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@@ -75,81 +69,96 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
|
|
||||||
private int roleid;
|
private int roleid;
|
||||||
private string name = string.Empty;
|
private string name = string.Empty;
|
||||||
private List<UserRole> users;
|
private AutoComplete user;
|
||||||
private int userid = -1;
|
private DateTime? effectivedate = null;
|
||||||
private DateTime? effectivedate = null;
|
private DateTime? expirydate = null;
|
||||||
private DateTime? expirydate = null;
|
private List<UserRole> userroles;
|
||||||
private List<UserRole> userroles;
|
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
roleid = Int32.Parse(PageState.QueryString["id"]);
|
roleid = Int32.Parse(PageState.QueryString["id"]);
|
||||||
Role role = await RoleService.GetRoleAsync(roleid);
|
Role role = await RoleService.GetRoleAsync(roleid);
|
||||||
name = role.Name;
|
name = role.Name;
|
||||||
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
await GetUserRoles();
|
||||||
await GetUserRoles();
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
catch (Exception ex)
|
{
|
||||||
{
|
await logger.LogError(ex, "Error Loading Users {Error}", ex.Message);
|
||||||
await logger.LogError(ex, "Error Loading Users {Error}", ex.Message);
|
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
|
||||||
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private async Task GetUserRoles()
|
private async Task<Dictionary<string, string>> GetUsers(string filter)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, name);
|
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||||
}
|
return users.Where(item => item.User.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase))
|
||||||
catch (Exception ex)
|
.ToDictionary(item => item.UserId.ToString(), item => item.User.DisplayName);
|
||||||
{
|
}
|
||||||
await logger.LogError(ex, "Error Loading User Roles {RoleId} {Error}", roleid, ex.Message);
|
catch (Exception ex)
|
||||||
AddModuleMessage(Localizer["Error.User.LoadRole"], MessageType.Error);
|
{
|
||||||
}
|
await logger.LogError(ex, "Error Loading Users {filter} {Error}", filter, ex.Message);
|
||||||
}
|
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
|
||||||
|
}
|
||||||
|
return new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SaveUserRole()
|
private async Task GetUserRoles()
|
||||||
{
|
{
|
||||||
validated = true;
|
try
|
||||||
var interop = new Interop(JSRuntime);
|
{
|
||||||
if (await interop.FormValid(form))
|
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, name);
|
||||||
{
|
}
|
||||||
try
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (userid != -1)
|
await logger.LogError(ex, "Error Loading User Roles {RoleId} {Error}", roleid, ex.Message);
|
||||||
{
|
AddModuleMessage(Localizer["Error.User.LoadRole"], MessageType.Error);
|
||||||
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
|
}
|
||||||
if (userrole != null)
|
}
|
||||||
{
|
|
||||||
userrole.EffectiveDate = effectivedate;
|
|
||||||
userrole.ExpiryDate = expirydate;
|
|
||||||
await UserRoleService.UpdateUserRoleAsync(userrole);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
userrole = new UserRole();
|
|
||||||
userrole.UserId = userid;
|
|
||||||
userrole.RoleId = roleid;
|
|
||||||
userrole.EffectiveDate = effectivedate;
|
|
||||||
userrole.ExpiryDate = expirydate;
|
|
||||||
|
|
||||||
await UserRoleService.AddUserRoleAsync(userrole);
|
private async Task SaveUserRole()
|
||||||
}
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(user.Key) && int.TryParse(user.Key, out int userid))
|
||||||
|
{
|
||||||
|
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
|
||||||
|
if (userrole != null)
|
||||||
|
{
|
||||||
|
userrole.EffectiveDate = effectivedate;
|
||||||
|
userrole.ExpiryDate = expirydate;
|
||||||
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
userrole = new UserRole();
|
||||||
|
userrole.UserId = userid;
|
||||||
|
userrole.RoleId = roleid;
|
||||||
|
userrole.EffectiveDate = effectivedate;
|
||||||
|
userrole.ExpiryDate = expirydate;
|
||||||
|
|
||||||
await logger.LogInformation("User Assigned To Role {UserRole}", userrole);
|
await UserRoleService.AddUserRoleAsync(userrole);
|
||||||
AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success);
|
}
|
||||||
await GetUserRoles();
|
|
||||||
StateHasChanged();
|
await logger.LogInformation("User Assigned To Role {UserRole}", userrole);
|
||||||
|
AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success);
|
||||||
|
await GetUserRoles();
|
||||||
|
user.Clear();
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -171,27 +180,28 @@ else
|
|||||||
|
|
||||||
private async Task DeleteUserRole(int UserRoleId)
|
private async Task DeleteUserRole(int UserRoleId)
|
||||||
{
|
{
|
||||||
validated = true;
|
try
|
||||||
var interop = new Interop(JSRuntime);
|
|
||||||
if (await interop.FormValid(form))
|
|
||||||
{
|
{
|
||||||
try
|
var userrole = await UserRoleService.GetUserRoleAsync(UserRoleId);
|
||||||
|
if (userrole.Role.Name == RoleNames.Registered)
|
||||||
|
{
|
||||||
|
userrole.ExpiryDate = DateTime.UtcNow;
|
||||||
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
||||||
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
|
await logger.LogInformation("User {Username} Removed From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success);
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
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
@@ -29,7 +29,7 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="alias" HelpText="Enter the aliases for the site. An alias can be a domain name (www.site.com) or a virtual folder (ie. www.site.com/folder). If a site has multiple aliases they can be separated by commas." ResourceKey="Aliases">Aliases: </Label>
|
<Label Class="col-sm-3" For="alias" HelpText="The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Urls: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
|
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,7 +37,7 @@ else
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label>
|
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="defaultTheme" class="form-select" @onchange="(e => ThemeChanged(e))" required>
|
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
||||||
<option value="-"><@Localizer["Theme.Select"]></option>
|
<option value="-"><@Localizer["Theme.Select"]></option>
|
||||||
@foreach (var theme in _themes)
|
@foreach (var theme in _themes)
|
||||||
{
|
{
|
||||||
@@ -58,19 +58,6 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="adminContainer" HelpText="Select the admin container for the site" ResourceKey="AdminContainer">Admin Container: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="adminContainer" class="form-select" @bind="@_admincontainertype" required>
|
|
||||||
<option value="-"><@Localizer["Container.Select"]></option>
|
|
||||||
<option value=""><@Localizer["DefaultContainer.Admin"]></option>
|
|
||||||
@foreach (var container in _containers)
|
|
||||||
{
|
|
||||||
<option value="@container.TypeName">@container.Name</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label>
|
<Label Class="col-sm-3" For="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -84,27 +71,29 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="runtime" HelpText="The runtime hosting model" ResourceKey="Runtime">Runtime: </Label>
|
<Label Class="col-sm-3" For="rendermode" HelpText="The default render mode for the site" ResourceKey="Rendermode">Render Mode: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="rendermode" class="form-select" @bind="@_rendermode" required>
|
||||||
|
<option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
|
||||||
|
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
|
||||||
|
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="runtime" HelpText="The render mode for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
||||||
<option value="Server">@SharedLocalizer["BlazorServer"]</option>
|
<option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
|
||||||
<option value="WebAssembly">@SharedLocalizer["BlazorWebAssembly"]</option>
|
<option value="@Runtimes.WebAssembly">@(SharedLocalizer["Runtime" + @Runtimes.WebAssembly])</option>
|
||||||
|
<option value="@Runtimes.Auto">@(SharedLocalizer["Runtime" + @Runtimes.Auto])</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="prerender" HelpText="Specifies if the site should be prerendered (for search crawlers, etc...)" ResourceKey="Prerender">Prerender? </Label>
|
<Label Class="col-sm-3" For="tenant" HelpText="Select the database for the site" ResourceKey="Tenant">Database: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="prerender" class="form-select" @bind="@_prerender" required>
|
<select id="tenant" class="form-select" value="@_tenantid" @onchange="(e => TenantChanged(e))" required>
|
||||||
<option value="Prerendered">@SharedLocalizer["Yes"]</option>
|
|
||||||
<option value="">@SharedLocalizer["No"]</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="tenant" HelpText="Select the tenant for the site" ResourceKey="Tenant">Tenant: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="tenant" class="form-select" @onchange="(e => TenantChanged(e))" required>
|
|
||||||
<option value="-"><@Localizer["Tenant.Select"]></option>
|
<option value="-"><@Localizer["Tenant.Select"]></option>
|
||||||
<option value="+"><@Localizer["Tenant.Add"]></option>
|
<option value="+"><@Localizer["Tenant.Add"]></option>
|
||||||
@foreach (Tenant tenant in _tenants)
|
@foreach (Tenant tenant in _tenants)
|
||||||
@@ -120,33 +109,51 @@ else
|
|||||||
<hr class="app-rule" />
|
<hr class="app-rule" />
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="name" HelpText="Enter the name for the tenant" ResourceKey="TenantName">Tenant Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="Enter the name for the database. Note that this will be the tenant name which is used within the framework to identify the database." ResourceKey="TenantName">Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="name" class="form-control" @bind="@_tenantName" maxlength="100" required />
|
<input id="name" class="form-control" @bind="@_tenantName" maxlength="100" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="databaseType" HelpText="Select the database type for the tenant" ResourceKey="DatabaseType">Database Type: </Label>
|
<Label Class="col-sm-3" For="databaseType" HelpText="Select the database type" ResourceKey="DatabaseType">Type: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
|
@if (_databases != null)
|
||||||
@foreach (var database in _databases)
|
{
|
||||||
{
|
<div class="input-group">
|
||||||
if (database.IsDefault)
|
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
|
||||||
{
|
@foreach (var database in _databases)
|
||||||
<option value="@database.Name" selected>@Localizer[@database.Name]</option>
|
{
|
||||||
}
|
<option value="@database.Name">@Localizer[@database.Name]</option>
|
||||||
else
|
}
|
||||||
{
|
</select>
|
||||||
<option value="@database.Name">@Localizer[@database.Name]</option>
|
@if (!_showConnectionString)
|
||||||
}
|
{
|
||||||
}
|
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
|
||||||
</select>
|
}
|
||||||
</div>
|
else
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
if (_databaseConfigType != null)
|
@if (!_showConnectionString)
|
||||||
{
|
{
|
||||||
@DatabaseConfigComponent;
|
if (_databaseConfigType != null)
|
||||||
}
|
{
|
||||||
|
@DatabaseConfigComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">Settings:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="connectionstring" class="form-control" @bind="@_connectionString" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="hostUsername" HelpText="Enter the username of an existing host user" ResourceKey="HostUsername">Host Username:</Label>
|
<Label Class="col-sm-3" For="hostUsername" HelpText="Enter the username of an existing host user" ResourceKey="HostUsername">Host Username:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -169,161 +176,191 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Database> _databases;
|
private List<Database> _databases;
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
private string _databaseName = "LocalDB";
|
private string _databaseName;
|
||||||
private Type _databaseConfigType;
|
private Type _databaseConfigType;
|
||||||
private object _databaseConfig;
|
private object _databaseConfig;
|
||||||
private RenderFragment DatabaseConfigComponent { get; set; }
|
private RenderFragment DatabaseConfigComponent { get; set; }
|
||||||
|
private bool _showConnectionString = false;
|
||||||
|
private string _connectionString = string.Empty;
|
||||||
|
|
||||||
|
private List<Theme> _themeList;
|
||||||
|
private List<ThemeControl> _themes = new List<ThemeControl>();
|
||||||
|
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||||
|
private List<SiteTemplate> _siteTemplates;
|
||||||
|
private List<Tenant> _tenants;
|
||||||
|
private string _tenantid = "-";
|
||||||
|
|
||||||
private List<Theme> _themeList;
|
private string _tenantName = string.Empty;
|
||||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
|
||||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
|
||||||
private List<SiteTemplate> _siteTemplates;
|
|
||||||
private List<Tenant> _tenants;
|
|
||||||
private string _tenantid = "-";
|
|
||||||
|
|
||||||
private string _tenantName = string.Empty;
|
private string _hostusername = string.Empty;
|
||||||
|
private string _hostpassword = string.Empty;
|
||||||
|
|
||||||
private string _hostusername = string.Empty;
|
private string _name = string.Empty;
|
||||||
private string _hostpassword = string.Empty;
|
private string _urls = string.Empty;
|
||||||
|
private string _themetype = "-";
|
||||||
|
private string _containertype = "-";
|
||||||
|
private string _sitetemplatetype = "-";
|
||||||
|
private string _rendermode = RenderModes.Static;
|
||||||
|
private string _runtime = Runtimes.Server;
|
||||||
|
|
||||||
private string _name = string.Empty;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
private string _urls = string.Empty;
|
|
||||||
private string _themetype = "-";
|
|
||||||
private string _containertype = "-";
|
|
||||||
private string _admincontainertype = "";
|
|
||||||
private string _sitetemplatetype = "-";
|
|
||||||
private string _runtime = "Server";
|
|
||||||
private string _prerender = "Prerendered";
|
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_tenants = await TenantService.GetTenantsAsync();
|
||||||
|
if (_tenants.Any(item => item.Name == TenantNames.Master))
|
||||||
|
{
|
||||||
|
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
|
||||||
|
}
|
||||||
|
_urls = PageState.Alias.Name;
|
||||||
|
_themeList = await ThemeService.GetThemesAsync();
|
||||||
|
_themes = ThemeService.GetThemeControls(_themeList);
|
||||||
|
if (_themes.Any(item => item.TypeName == Constants.DefaultTheme))
|
||||||
|
{
|
||||||
|
_themetype = Constants.DefaultTheme;
|
||||||
|
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
||||||
|
_containertype = _containers.First().TypeName;
|
||||||
|
}
|
||||||
|
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
|
||||||
|
if (_siteTemplates.Any(item => item.TypeName == Constants.DefaultSiteTemplate))
|
||||||
|
{
|
||||||
|
_sitetemplatetype = Constants.DefaultSiteTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
_databases = await DatabaseService.GetDatabasesAsync();
|
||||||
{
|
if (_databases.Exists(item => item.IsDefault))
|
||||||
_tenants = await TenantService.GetTenantsAsync();
|
{
|
||||||
_urls = PageState.Alias.Name;
|
_databaseName = _databases.Find(item => item.IsDefault).Name;
|
||||||
_themeList = await ThemeService.GetThemesAsync();
|
}
|
||||||
_themes = ThemeService.GetThemeControls(_themeList);
|
else
|
||||||
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
|
{
|
||||||
_databases = await DatabaseService.GetDatabasesAsync();
|
_databaseName = "LocalDB";
|
||||||
LoadDatabaseConfigComponent();
|
}
|
||||||
}
|
LoadDatabaseConfigComponent();
|
||||||
|
}
|
||||||
|
|
||||||
private void DatabaseChanged(ChangeEventArgs eventArgs)
|
private void DatabaseChanged(ChangeEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_databaseName = (string)eventArgs.Value;
|
_databaseName = (string)eventArgs.Value;
|
||||||
|
_showConnectionString = false;
|
||||||
|
LoadDatabaseConfigComponent();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LoadDatabaseConfigComponent();
|
private void LoadDatabaseConfigComponent()
|
||||||
}
|
{
|
||||||
catch
|
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||||
{
|
if (database != null)
|
||||||
AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
|
{
|
||||||
}
|
_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()
|
private void TenantChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
_tenantid = (string)e.Value;
|
||||||
if (database != null)
|
if (string.IsNullOrEmpty(_tenantName))
|
||||||
{
|
{
|
||||||
_databaseConfigType = Type.GetType(database.ControlType);
|
_tenantName = _name;
|
||||||
DatabaseConfigComponent = builder =>
|
}
|
||||||
{
|
StateHasChanged();
|
||||||
builder.OpenComponent(0, _databaseConfigType);
|
}
|
||||||
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
|
|
||||||
builder.CloseComponent();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TenantChanged(ChangeEventArgs e)
|
private async void ThemeChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_tenantid = (string)e.Value;
|
try
|
||||||
if (string.IsNullOrEmpty(_tenantName))
|
{
|
||||||
{
|
_themetype = (string)e.Value;
|
||||||
_tenantName = _name;
|
if (_themetype != "-")
|
||||||
}
|
{
|
||||||
StateHasChanged();
|
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
||||||
}
|
_containertype = _containers.First().TypeName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_containers = new List<ThemeControl>();
|
||||||
|
_containertype = "-";
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Theme.LoadContainers"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async void ThemeChanged(ChangeEventArgs e)
|
private async Task SaveSite()
|
||||||
{
|
{
|
||||||
try
|
validated = true;
|
||||||
{
|
var interop = new Interop(JSRuntime);
|
||||||
_themetype = (string)e.Value;
|
if (await interop.FormValid(form))
|
||||||
if (_themetype != "-")
|
{
|
||||||
{
|
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
|
||||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
{
|
||||||
}
|
_urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
|
||||||
else
|
var duplicates = new List<string>();
|
||||||
{
|
var aliases = await AliasService.GetAliasesAsync();
|
||||||
_containers = new List<ThemeControl>();
|
foreach (string name in _urls.Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||||
}
|
{
|
||||||
_containertype = "-";
|
if (aliases.Exists(item => item.Name == name))
|
||||||
_admincontainertype = "";
|
{
|
||||||
StateHasChanged();
|
duplicates.Add(name);
|
||||||
}
|
}
|
||||||
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()
|
if (duplicates.Count == 0)
|
||||||
{
|
{
|
||||||
validated = true;
|
InstallConfig config = new InstallConfig();
|
||||||
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(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
|
||||||
{
|
|
||||||
if (aliases.Exists(item => item.Name == name))
|
|
||||||
{
|
|
||||||
duplicates.Add(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duplicates.Count == 0)
|
if (_tenantid == "+")
|
||||||
{
|
{
|
||||||
InstallConfig config = new InstallConfig();
|
if (!string.IsNullOrEmpty(_tenantName) && !_tenants.Exists(item => item.Name == _tenantName))
|
||||||
|
{
|
||||||
if (_tenantid == "+")
|
// validate host credentials
|
||||||
{
|
var user = new User();
|
||||||
if (!string.IsNullOrEmpty(_tenantName) && _tenants.FirstOrDefault(item => item.Name == _tenantName) == null)
|
user.SiteId = PageState.Site.SiteId;
|
||||||
{
|
user.Username = _hostusername;
|
||||||
// validate host credentials
|
user.Password = _hostpassword;
|
||||||
var user = new User();
|
user.LastIPAddress = PageState.RemoteIPAddress;
|
||||||
user.SiteId = PageState.Site.SiteId;
|
user = await UserService.LoginUserAsync(user, false, false);
|
||||||
user.Username = _hostusername;
|
|
||||||
user.Password = _hostpassword;
|
|
||||||
user.LastIPAddress = PageState.RemoteIPAddress;
|
|
||||||
user = await UserService.LoginUserAsync(user);
|
|
||||||
if (user.IsAuthenticated)
|
if (user.IsAuthenticated)
|
||||||
{
|
{
|
||||||
var connectionString = String.Empty;
|
|
||||||
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
|
||||||
{
|
|
||||||
connectionString = databaseConfigControl.GetConnectionString();
|
|
||||||
}
|
|
||||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
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 != "")
|
if (connectionString != "")
|
||||||
{
|
{
|
||||||
config.TenantName = _tenantName;
|
config.TenantName = _tenantName;
|
||||||
config.DatabaseType = database.DBType;
|
config.DatabaseType = database.DBType;
|
||||||
config.ConnectionString = connectionString;
|
config.ConnectionString = connectionString;
|
||||||
config.HostUsername = _hostusername;
|
config.HostUsername = _hostusername;
|
||||||
config.HostPassword = _hostpassword;
|
config.HostPassword = _hostpassword;
|
||||||
config.HostEmail = user.Email;
|
config.HostEmail = user.Email;
|
||||||
config.HostName = user.DisplayName;
|
config.HostName = user.DisplayName;
|
||||||
@@ -362,10 +399,10 @@ else
|
|||||||
config.Aliases = _urls;
|
config.Aliases = _urls;
|
||||||
config.DefaultTheme = _themetype;
|
config.DefaultTheme = _themetype;
|
||||||
config.DefaultContainer = _containertype;
|
config.DefaultContainer = _containertype;
|
||||||
config.DefaultAdminContainer = _admincontainertype;
|
config.DefaultAdminContainer = "";
|
||||||
config.SiteTemplate = _sitetemplatetype;
|
config.SiteTemplate = _sitetemplatetype;
|
||||||
|
config.RenderMode = _rendermode;
|
||||||
config.Runtime = _runtime;
|
config.Runtime = _runtime;
|
||||||
config.RenderMode = _runtime + _prerender;
|
|
||||||
|
|
||||||
ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
|
|
||||||
@@ -397,4 +434,13 @@ else
|
|||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ToggleConnectionString()
|
||||||
|
{
|
||||||
|
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||||
|
{
|
||||||
|
_connectionString = databaseConfigControl.GetConnectionString();
|
||||||
|
}
|
||||||
|
_showConnectionString = !_showConnectionString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,17 +8,17 @@
|
|||||||
|
|
||||||
@if (_sites == null)
|
@if (_sites == null)
|
||||||
{
|
{
|
||||||
<p><em>Loading...</em></p>
|
<p><em>@SharedLocalizer["Loading"]</em></p>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Add Site" ResourceKey="AddSite" />
|
<ActionLink Action="Add" Text="Add Site" ResourceKey="AddSite" />
|
||||||
|
|
||||||
<Pager Items="@_sites">
|
<Pager Items="@_sites" SearchProperties="Name">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@Localizer["AliasName"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><button type="button" class="btn btn-primary" @onclick="@(async () => Edit(context.Name))">@SharedLocalizer["Edit"]</button></td>
|
<td><button type="button" class="btn btn-primary" @onclick="@(async () => Edit(context.Name))">@SharedLocalizer["Edit"]</button></td>
|
||||||
@@ -48,11 +48,25 @@ else
|
|||||||
|
|
||||||
private void Edit(string name)
|
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)
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Sql
|
@namespace Oqtane.Modules.Admin.Sql
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject ISystemService SystemService
|
||||||
@inject ITenantService TenantService
|
@inject ITenantService TenantService
|
||||||
@inject IDatabaseService DatabaseService
|
@inject IDatabaseService DatabaseService
|
||||||
@inject ISqlService SqlService
|
@inject ISqlService SqlService
|
||||||
@@ -14,125 +15,298 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="connection" HelpText="Select a database connection (from appsettings.json)" ResourceKey="Connection">Connection: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="tenant" class="form-select" value="@_connection" @onchange="(e => ConnectionChanged(e))">
|
||||||
|
<option value="-"><@Localizer["Connection.Select"]></option>
|
||||||
|
<option value="+"><@Localizer["Connection.Add"]></option>
|
||||||
|
@foreach (var connection in _connections)
|
||||||
|
{
|
||||||
|
<option value="@connection.Key">@connection.Key</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (_connection == "+")
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="name" HelpText="Enter the name of the connection" ResourceKey="Name">Name: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="databasetype" HelpText="Select the database type" ResourceKey="DatabaseType">Type: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
@if (_databases != null)
|
||||||
|
{
|
||||||
|
<div class="input-group">
|
||||||
|
<select id="databasetype" class="form-select" value="@_databasetype" @onchange="(e => DatabaseTypeChanged(e))" required>
|
||||||
|
@foreach (var database in _databases)
|
||||||
|
{
|
||||||
|
<option value="@database.Name">@Localizer[@database.Name]</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
@if (!_showConnectionString)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="ShowConnectionString">@Localizer["EnterConnectionString"]</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="ShowConnectionString">@Localizer["EnterConnectionParameters"]</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (!_showConnectionString)
|
||||||
|
{
|
||||||
|
if (_databaseConfigType != null)
|
||||||
|
{
|
||||||
|
@DatabaseConfigComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="connectionstring" HelpText="Enter a complete connection string including all parameters and delimiters" ResourceKey="ConnectionString">Settings:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="Add">@Localizer["Add"]</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (_connection != "-")
|
||||||
|
{
|
||||||
|
@if (!string.IsNullOrEmpty(_tenant))
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="databasetype" HelpText="The database type" ResourceKey="DatabaseType">Type: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="databasetype" class="form-control" @bind="@_databasetype" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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 />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="connectionstring" HelpText="The connection string" ResourceKey="ConnectionString">Settings: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="connectionstring" type="@_connectionstringtype" class="form-control" @bind="@_connectionstring" readonly />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@ToggleConnectionString">@_connectionstringtoggle</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="sqlQuery" HelpText="Enter a valid SQL query for the database" ResourceKey="SqlQuery">SQL Query: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="sqlQuery" class="form-control" @bind="@_sql" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="Execute">@Localizer["Execute"]</button>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
@if (_results != null)
|
||||||
|
{
|
||||||
|
@if (_results.Count > 0)
|
||||||
|
{
|
||||||
|
<Pager Class="table table-bordered" Items="@_results">
|
||||||
|
<Header>
|
||||||
|
@foreach (KeyValuePair<string, string> kvp in _results.First())
|
||||||
|
{
|
||||||
|
<th>@kvp.Key</th>
|
||||||
|
}
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
@foreach (KeyValuePair<string, string> kvp in context)
|
||||||
|
{
|
||||||
|
<td>@kvp.Value</td>
|
||||||
|
}
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@Localizer["Return.NoResult"]
|
||||||
|
}
|
||||||
|
<br />
|
||||||
|
|
||||||
<div class="row mb-1 align-items-center">
|
<br />
|
||||||
<Label Class="col-sm-3" For="tenant" HelpText="Select the tenant for the SQL server" ResourceKey="Tenant">Tenant: </Label>
|
}
|
||||||
<div class="col-sm-9">
|
}
|
||||||
<select id="tenant" class="form-select" value="@_tenantid" @onchange="(e => TenantChanged(e))">
|
}
|
||||||
<option value="-1"><@Localizer["Tenant.Select"]></option>
|
</div>
|
||||||
@foreach (Tenant tenant in _tenants)
|
|
||||||
{
|
|
||||||
<option value="@tenant.TenantId">@tenant.Name</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@if (_tenantid != "-1")
|
|
||||||
{
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="database" HelpText="The database for the tenant" ResourceKey="Database">Database: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="database" class="form-control" @bind="@_database" readonly />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="connectionstring" HelpText="The connection information for the database" ResourceKey="ConnectionString">Connection: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="2" readonly></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="sqlQeury" HelpText="Enter the query for the SQL server" ResourceKey="SqlQuery">SQL Query: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<textarea id="sqlQeury" class="form-control" @bind="@_sql" rows="3"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<button type="button" class="btn btn-success" @onclick="Execute">@Localizer["Execute"]</button>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
@if (_results != null)
|
|
||||||
{
|
|
||||||
@if (_results.Count > 0)
|
|
||||||
{
|
|
||||||
<Pager Class="table table-bordered" Items="@_results">
|
|
||||||
<Header>
|
|
||||||
@foreach (KeyValuePair<string, string> kvp in _results.First())
|
|
||||||
{
|
|
||||||
<th>@kvp.Key</th>
|
|
||||||
}
|
|
||||||
</Header>
|
|
||||||
<Row>
|
|
||||||
@foreach (KeyValuePair<string, string> kvp in context)
|
|
||||||
{
|
|
||||||
<td>@kvp.Value</td>
|
|
||||||
}
|
|
||||||
</Row>
|
|
||||||
</Pager>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@Localizer["Return.NoResult"]
|
|
||||||
}
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Tenant> _tenants;
|
private string _connection = "-";
|
||||||
private string _tenantid = "-1";
|
private Dictionary<string, object> _connections;
|
||||||
private string _database = string.Empty;
|
private List<Tenant> _tenants;
|
||||||
private string _connectionstring = string.Empty;
|
private List<Database> _databases;
|
||||||
private string _sql = string.Empty;
|
|
||||||
private List<Dictionary<string, string>> _results;
|
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
private string _name = string.Empty;
|
||||||
|
private string _databasetype = string.Empty;
|
||||||
|
private Type _databaseConfigType;
|
||||||
|
private object _databaseConfig;
|
||||||
|
private RenderFragment DatabaseConfigComponent { get; set; }
|
||||||
|
private bool _showConnectionString = false;
|
||||||
|
private string _tenant = string.Empty;
|
||||||
|
private string _connectionstring = string.Empty;
|
||||||
|
private string _connectionstringtype = "password";
|
||||||
|
private string _connectionstringtoggle = string.Empty;
|
||||||
|
private string _sql = string.Empty;
|
||||||
|
private List<Dictionary<string, string>> _results;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_tenants = await TenantService.GetTenantsAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message);
|
|
||||||
AddModuleMessage(ex.Message, MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void TenantChanged(ChangeEventArgs e)
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_tenantid = (string)e.Value;
|
_connections = await SystemService.GetSystemInfoAsync("connectionstrings");
|
||||||
var tenants = await TenantService.GetTenantsAsync();
|
_tenants = await TenantService.GetTenantsAsync();
|
||||||
var _databases = await DatabaseService.GetDatabasesAsync();
|
_databases = await DatabaseService.GetDatabasesAsync();
|
||||||
var tenant = tenants.Find(item => item.TenantId == int.Parse(_tenantid));
|
_connectionstringtoggle = SharedLocalizer["ShowPassword"];
|
||||||
if (tenant != null)
|
}
|
||||||
{
|
catch (Exception ex)
|
||||||
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
|
{
|
||||||
_connectionstring = tenant.DBConnectionString;
|
await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message);
|
||||||
}
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
StateHasChanged();
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Loading Tenant {TenantId} {Error}", _tenantid, ex.Message);
|
|
||||||
AddModuleMessage(ex.Message, MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Execute()
|
private async void ConnectionChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_tenantid != "-1" && !string.IsNullOrEmpty(_sql))
|
_connection = (string)e.Value;
|
||||||
{
|
if (_connection != "-" && _connection != "+")
|
||||||
var sqlquery = new SqlQuery { TenantId = int.Parse(_tenantid), Query = _sql };
|
{
|
||||||
|
_connectionstring = _connections[_connection].ToString();
|
||||||
|
_tenant = "";
|
||||||
|
_databasetype = "";
|
||||||
|
var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection);
|
||||||
|
if (tenant != null)
|
||||||
|
{
|
||||||
|
_tenant = tenant.Name;
|
||||||
|
_databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType && item.Name != "LocalDB").Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_databases.Exists(item => item.IsDefault))
|
||||||
|
{
|
||||||
|
_databasetype = _databases.Find(item => item.IsDefault).Name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_databasetype = "LocalDB";
|
||||||
|
}
|
||||||
|
_showConnectionString = false;
|
||||||
|
LoadDatabaseConfigComponent();
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading Connection {Connection} {Error}", _connection, ex.Message);
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DatabaseTypeChanged(ChangeEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_databasetype = (string)eventArgs.Value;
|
||||||
|
_showConnectionString = false;
|
||||||
|
LoadDatabaseConfigComponent();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadDatabaseConfigComponent()
|
||||||
|
{
|
||||||
|
var database = _databases.SingleOrDefault(d => d.Name == _databasetype);
|
||||||
|
if (database != null)
|
||||||
|
{
|
||||||
|
_databaseConfigType = Type.GetType(database.ControlType);
|
||||||
|
DatabaseConfigComponent = builder =>
|
||||||
|
{
|
||||||
|
builder.OpenComponent(0, _databaseConfigType);
|
||||||
|
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
|
||||||
|
builder.CloseComponent();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowConnectionString()
|
||||||
|
{
|
||||||
|
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||||
|
{
|
||||||
|
_connectionstring = databaseConfigControl.GetConnectionString();
|
||||||
|
}
|
||||||
|
_showConnectionString = !_showConnectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Add()
|
||||||
|
{
|
||||||
|
var connectionstring = _connectionstring;
|
||||||
|
if (!_showConnectionString && _databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||||
|
{
|
||||||
|
connectionstring = databaseConfigControl.GetConnectionString();
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(connectionstring))
|
||||||
|
{
|
||||||
|
var settings = new Dictionary<string, object>();
|
||||||
|
settings.Add($"{SettingKeys.ConnectionStringsSection}:{_name}", connectionstring);
|
||||||
|
await SystemService.UpdateSystemInfoAsync(settings);
|
||||||
|
_connections = await SystemService.GetSystemInfoAsync("connectionstrings");
|
||||||
|
_connection = "-";
|
||||||
|
AddModuleMessage(Localizer["Message.Connection.Added"], MessageType.Success);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Required.Connection"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleConnectionString()
|
||||||
|
{
|
||||||
|
if (_connectionstringtype == "password")
|
||||||
|
{
|
||||||
|
_connectionstringtype = "text";
|
||||||
|
_connectionstringtoggle = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_connectionstringtype = "password";
|
||||||
|
_connectionstringtoggle = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Execute()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_databasetype != "-" && !string.IsNullOrEmpty(_sql))
|
||||||
|
{
|
||||||
|
var dbtype = _databases.FirstOrDefault(item => item.Name == _databasetype).DBType;
|
||||||
|
var sqlquery = new SqlQuery { DBConnectionString = _connection, DBType = dbtype, Query = _sql };
|
||||||
sqlquery = await SqlService.ExecuteQueryAsync(sqlquery);
|
sqlquery = await SqlService.ExecuteQueryAsync(sqlquery);
|
||||||
_results = sqlquery.Results;
|
_results = sqlquery.Results;
|
||||||
AddModuleMessage(Localizer["Success.QueryExecuted"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.QueryExecuted"], MessageType.Success);
|
||||||
|
|||||||
@@ -26,6 +26,12 @@
|
|||||||
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
|
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="process" HelpText="Indicates if the current process is 32 bit or 64 bit" ResourceKey="Process">Process: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="process" class="form-control" @bind="@_process" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
|
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -38,7 +44,13 @@
|
|||||||
<input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly />
|
<input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="environment" HelpText="Environment name" ResourceKey="Environment">Environment: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="environment" class="form-control" @bind="@_environment" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="contentrootpath" HelpText="Root Path" ResourceKey="ContentRootPath">Root Path: </Label>
|
<Label Class="col-sm-3" For="contentrootpath" HelpText="Root Path" ResourceKey="ContentRootPath">Root Path: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="contentrootpath" class="form-control" @bind="@_contentrootpath" readonly />
|
<input id="contentrootpath" class="form-control" @bind="@_contentrootpath" readonly />
|
||||||
@@ -56,12 +68,6 @@
|
|||||||
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
|
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="tickcount" HelpText="Amount Of Time The Service Has Been Available And Operational" ResourceKey="TickCount">Service Uptime: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="tickcount" class="form-control" @bind="@_tickcount" readonly />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
|
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@@ -127,21 +133,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="packageservice" HelpText="Specify If The Package Service Is Enabled For Installing Modules, Themes, And Translations" ResourceKey="PackageService">Enable Package Service? </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">
|
<div class="col-sm-9">
|
||||||
<select id="packageservice" class="form-select" @bind="@_packageservice">
|
<input id="cachecontrol" class="form-control" @bind="@_cachecontrol" />
|
||||||
<option value="true">@SharedLocalizer["True"]</option>
|
</div>
|
||||||
<option value="false">@SharedLocalizer["False"]</option>
|
</div>
|
||||||
</select>
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="packageregistryurl" HelpText="Specify The Url Of The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager Url: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="packageregistryemail" HelpText="Specify The Email Address Of The User Account Used For Interacting With The Package Manager Service. This Account Is Used For Managing Packages Across Multiple Installations." ResourceKey="PackageManagerEmail">Package Manager Email: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="packageregistryemail" class="form-control" @bind="@_packageregistryemail" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>
|
||||||
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Access.ApiFramework"]</a>
|
|
||||||
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
|
<ActionDialog Header="Restart Application" Message="Are You Sure You Wish To Restart The Application?" Action="Restart Application" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await RestartApplication())" ResourceKey="RestartApplication" />
|
||||||
|
<br /><br />
|
||||||
|
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Swagger"]</a>
|
||||||
|
<a class="btn btn-secondary" href="api/endpoint" target="_new">@Localizer["Endpoints"]</a>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="log" HelpText="System log information for current day" ResourceKey="Log">Log: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="log" class="form-control" rows="10" @bind="@_log" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
<button type="button" class="btn btn-danger" @onclick="ClearLog">@Localizer["Clear"]</button>
|
||||||
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
@@ -149,12 +179,13 @@
|
|||||||
private string _version = string.Empty;
|
private string _version = string.Empty;
|
||||||
private string _clrversion = string.Empty;
|
private string _clrversion = string.Empty;
|
||||||
private string _osversion = string.Empty;
|
private string _osversion = string.Empty;
|
||||||
private string _machinename = string.Empty;
|
private string _process = string.Empty;
|
||||||
|
private string _machinename = string.Empty;
|
||||||
private string _ipaddress = string.Empty;
|
private string _ipaddress = string.Empty;
|
||||||
|
private string _environment = string.Empty;
|
||||||
private string _contentrootpath = string.Empty;
|
private string _contentrootpath = string.Empty;
|
||||||
private string _webrootpath = string.Empty;
|
private string _webrootpath = string.Empty;
|
||||||
private string _servertime = string.Empty;
|
private string _servertime = string.Empty;
|
||||||
private string _tickcount = string.Empty;
|
|
||||||
private string _workingset = string.Empty;
|
private string _workingset = string.Empty;
|
||||||
private string _installationid = string.Empty;
|
private string _installationid = string.Empty;
|
||||||
|
|
||||||
@@ -162,57 +193,89 @@
|
|||||||
private string _logginglevel = string.Empty;
|
private string _logginglevel = string.Empty;
|
||||||
private string _notificationlevel = string.Empty;
|
private string _notificationlevel = string.Empty;
|
||||||
private string _swagger = string.Empty;
|
private string _swagger = string.Empty;
|
||||||
private string _packageservice = string.Empty;
|
private string _cachecontrol = string.Empty;
|
||||||
|
private string _packageregistryurl = string.Empty;
|
||||||
|
private string _packageregistryemail = string.Empty;
|
||||||
|
|
||||||
|
private string _log = string.Empty;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_version = Constants.Version;
|
_version = Constants.Version;
|
||||||
|
|
||||||
Dictionary<string, object> systeminfo = await SystemService.GetSystemInfoAsync("environment");
|
var systeminfo = await SystemService.GetSystemInfoAsync("environment");
|
||||||
if (systeminfo != null)
|
if (systeminfo != null)
|
||||||
{
|
{
|
||||||
_clrversion = systeminfo["CLRVersion"].ToString();
|
_clrversion = systeminfo["CLRVersion"].ToString();
|
||||||
_osversion = systeminfo["OSVersion"].ToString();
|
_osversion = systeminfo["OSVersion"].ToString();
|
||||||
_machinename = systeminfo["MachineName"].ToString();
|
_process = systeminfo["Process"].ToString();
|
||||||
|
_machinename = systeminfo["MachineName"].ToString();
|
||||||
_ipaddress = systeminfo["IPAddress"].ToString();
|
_ipaddress = systeminfo["IPAddress"].ToString();
|
||||||
|
_environment = systeminfo["Environment"].ToString();
|
||||||
_contentrootpath = systeminfo["ContentRootPath"].ToString();
|
_contentrootpath = systeminfo["ContentRootPath"].ToString();
|
||||||
_webrootpath = systeminfo["WebRootPath"].ToString();
|
_webrootpath = systeminfo["WebRootPath"].ToString();
|
||||||
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
|
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
|
||||||
_tickcount = TimeSpan.FromMilliseconds(Convert.ToInt64(systeminfo["TickCount"].ToString())).ToString();
|
|
||||||
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
|
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
|
||||||
}
|
}
|
||||||
|
|
||||||
systeminfo = await SystemService.GetSystemInfoAsync();
|
systeminfo = await SystemService.GetSystemInfoAsync("configuration");
|
||||||
if (systeminfo != null)
|
if (systeminfo != null)
|
||||||
{
|
{
|
||||||
_installationid = systeminfo["InstallationId"].ToString();
|
_installationid = systeminfo["InstallationId"].ToString();
|
||||||
_detailederrors = systeminfo["DetailedErrors"].ToString();
|
_detailederrors = systeminfo["DetailedErrors"].ToString();
|
||||||
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
|
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
|
||||||
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
|
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
|
||||||
_swagger = systeminfo["UseSwagger"].ToString();
|
_swagger = systeminfo["UseSwagger"].ToString();
|
||||||
_packageservice = systeminfo["PackageService"].ToString();
|
_cachecontrol = systeminfo["CacheControl"].ToString();
|
||||||
|
_packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
|
||||||
|
_packageregistryemail = systeminfo["PackageRegistryEmail"].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
systeminfo = await SystemService.GetSystemInfoAsync("log");
|
||||||
|
if (systeminfo != null)
|
||||||
|
{
|
||||||
|
_log = systeminfo["Log"].ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveConfig()
|
private async Task SaveConfig()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var settings = new Dictionary<string, object>();
|
var settings = new Dictionary<string, object>();
|
||||||
settings.Add("DetailedErrors", _detailederrors);
|
settings.Add("DetailedErrors", _detailederrors);
|
||||||
settings.Add("Logging:LogLevel:Default", _logginglevel);
|
settings.Add("Logging:LogLevel:Default", _logginglevel);
|
||||||
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
|
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
|
||||||
settings.Add("UseSwagger", _swagger);
|
settings.Add("UseSwagger", _swagger);
|
||||||
settings.Add("PackageService", _packageservice);
|
settings.Add("CacheControl", _cachecontrol);
|
||||||
|
settings.Add("PackageRegistryUrl", _packageregistryurl);
|
||||||
|
settings.Add("PackageRegistryEmail", _packageregistryemail);
|
||||||
await SystemService.UpdateSystemInfoAsync(settings);
|
await SystemService.UpdateSystemInfoAsync(settings);
|
||||||
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Saving Configuration");
|
await logger.LogError(ex, "Error Saving Configuration");
|
||||||
AddModuleMessage(Localizer["Error.UpdateConfig"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.UpdateConfig"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ClearLog()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = new Dictionary<string, object>();
|
||||||
|
settings.Add("clearlog", "true");
|
||||||
|
await SystemService.UpdateSystemInfoAsync(settings);
|
||||||
|
_log = string.Empty;
|
||||||
|
AddModuleMessage(Localizer["Success.ClearLog"], MessageType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Clearing Log");
|
||||||
|
AddModuleMessage(Localizer["Error.ClearLog"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RestartApplication()
|
private async Task RestartApplication()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,67 +10,124 @@
|
|||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Download" ResourceKey="Download">
|
<TabPanel Name="Download" ResourceKey="Download">
|
||||||
<div class="row justify-content-center mb-3">
|
<div class="row justify-content-center mb-3">
|
||||||
<div class="col-sm-6">
|
<div class="text-center">
|
||||||
<div class="input-group">
|
<div class="form-check form-check-inline">
|
||||||
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
|
<input id="free" class="form-check-input" type="radio" checked="@(_price == "free")" name="Price" @onchange="@(() => PriceChanged("free"))" />
|
||||||
<option value="free">@SharedLocalizer["Free"]</option>
|
<label class="form-check-label" for="free">@SharedLocalizer["Free"]</label>
|
||||||
<option value="paid">@SharedLocalizer["Paid"]</option>
|
</div>
|
||||||
</select>
|
<div class="form-check form-check-inline">
|
||||||
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
|
<input id="paid" class="form-check-input" type="radio" checked="@(_price == "paid")" name="Price" @onchange="@(() => PriceChanged("paid"))" />
|
||||||
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
|
<label class="form-check-label" for="paid">@SharedLocalizer["Paid"]</label>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row justify-content-center mb-3">
|
||||||
@if (_packages != null)
|
<div class="col">
|
||||||
{
|
<div class="input-group">
|
||||||
if (_packages.Count > 0)
|
<span class="input-group-text">@Localizer["Product"]</span>
|
||||||
{
|
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
|
||||||
<Pager Items="@_packages">
|
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
|
||||||
<Row>
|
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
|
||||||
<td>
|
<button type="button" class="btn btn-primary ms-2" @onclick="Refresh"><span class="@Icons.Reload" aria-hidden="true"></span></button>
|
||||||
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3> @SharedLocalizer["Search.By"]: <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
|
|
||||||
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
|
||||||
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"] |
|
|
||||||
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong> |
|
|
||||||
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
|
|
||||||
@((MarkupString)(context.TrialPeriod > 0 ? " | <strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
|
|
||||||
</td>
|
|
||||||
<td style="width: 1px; vertical-align: middle;">
|
|
||||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
<td style="width: 1px; vertical-align: middle;">
|
|
||||||
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
|
|
||||||
{
|
|
||||||
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</Row>
|
|
||||||
</Pager>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<br />
|
|
||||||
<div class="mx-auto text-center">
|
|
||||||
@Localizer["Search.NoResults"]
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
}
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col">
|
||||||
|
@if (_initialized)
|
||||||
|
{
|
||||||
|
<br />
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<h3>@((_packages != null) ? _packages.Count : 0) @SharedLocalizer["Search.Results"]</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<select class="form-select" value="@_sort" @onchange="(e => SortChanged(e))">
|
||||||
|
<option value="popularity">@SharedLocalizer["Search.Popularity"]</option>
|
||||||
|
<option value="alphabetical">@SharedLocalizer["Search.Alphabetical"]</option>
|
||||||
|
@if (_price == "free")
|
||||||
|
{
|
||||||
|
<option value="downloads">@SharedLocalizer["Search.Downloads"]</option>
|
||||||
|
}
|
||||||
|
<option value="recent">@SharedLocalizer["Search.RecentlyReleased"]</option>
|
||||||
|
@if (_price == "paid")
|
||||||
|
{
|
||||||
|
<option value="price">@SharedLocalizer["Search.Price"]</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Pager Format="Grid" Items="@_packages" DisplayPages="1" PageSize="9" Toolbar="Both" Class="container-fluid px-0" RowClass="row g-0" ColumnClass="col-lg-4 col-md-6">
|
||||||
|
<Row>
|
||||||
|
<div class="m-2 p-2 d-flex justify-content-center">
|
||||||
|
<div class="container-fluid px-0">
|
||||||
|
<div class="row g-0 mb-2">
|
||||||
|
<div class="col-4">
|
||||||
|
<a href="@context.ProductUrl" target="_blank">
|
||||||
|
@if (context.LogoUrl != null)
|
||||||
|
{
|
||||||
|
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<img src="/package.png" class="img-fluid" alt="@context.Name" />
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 text-end">
|
||||||
|
<small>@SharedLocalizer["Search.Version"]:</small> <strong>@context.Version</strong>
|
||||||
|
<br /><small>@SharedLocalizer["Search.Released"]:</small> <strong>@context.ReleaseDate.ToString("MM/dd/yyyy")</strong>
|
||||||
|
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||||
|
{
|
||||||
|
<br /><small>@SharedLocalizer["Search.Source"]:</small> <strong>@(new Uri(context.PackageUrl).Host)</strong>
|
||||||
|
}
|
||||||
|
@if (context.Price == null)
|
||||||
|
{
|
||||||
|
<br /><small>@SharedLocalizer["Search.Downloads"]:</small> <strong>@(String.Format("{0:n0}", context.Downloads))</strong>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<br /><small>@SharedLocalizer["From"]:</small> <strong>@context.Price.Value.ToString("$#,##0.00")</strong>
|
||||||
|
@((MarkupString)(context.TrialPeriod > 0 ? " <strong>(" + context.TrialPeriod + " Day Trial)</strong>" : ""))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row g-0">
|
||||||
|
<div class="col">
|
||||||
|
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_blank">@context.Name</a></h3><br />
|
||||||
|
<small>@SharedLocalizer["Search.By"]:</small> <strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
|
||||||
|
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
||||||
|
<br />
|
||||||
|
@if (!string.IsNullOrEmpty(context.PackageUrl))
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
||||||
|
}
|
||||||
|
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
|
||||||
|
{
|
||||||
|
<a class="btn btn-success ms-2" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@SharedLocalizer["Buy"]</a>
|
||||||
|
}
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Upload" ResourceKey="Upload">
|
<TabPanel Name="Upload" ResourceKey="Upload">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" HelpText="Upload one or more theme packages. Once they are uploaded click Install to complete the installation." ResourceKey="Theme">Theme: </Label>
|
<Label Class="col-sm-3" HelpText="Upload one or more theme packages." ResourceKey="Theme">Theme: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" />
|
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,12 +167,14 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<button type="button" class="btn btn-success" @onclick="InstallThemes">@SharedLocalizer["Install"]</button>
|
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private bool _initialized = false;
|
||||||
|
private int _page = 1;
|
||||||
private List<Package> _packages;
|
private List<Package> _packages;
|
||||||
private string _price = "free";
|
private string _price = "free";
|
||||||
|
private string _sort = "popularity";
|
||||||
private string _search = "";
|
private string _search = "";
|
||||||
private string _productname = "";
|
private string _productname = "";
|
||||||
private string _license = "";
|
private string _license = "";
|
||||||
@@ -129,6 +188,7 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LoadThemes();
|
await LoadThemes();
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -139,8 +199,10 @@
|
|||||||
|
|
||||||
private async Task LoadThemes()
|
private async Task LoadThemes()
|
||||||
{
|
{
|
||||||
|
ShowProgressIndicator();
|
||||||
|
|
||||||
var themes = await ThemeService.GetThemesAsync();
|
var themes = await ThemeService.GetThemesAsync();
|
||||||
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "");
|
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "", _sort);
|
||||||
|
|
||||||
if (_packages != null)
|
if (_packages != null)
|
||||||
{
|
{
|
||||||
@@ -152,46 +214,44 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void PriceChanged(ChangeEventArgs e)
|
private async void PriceChanged(string price)
|
||||||
{
|
{
|
||||||
try
|
_price = price;
|
||||||
{
|
_sort = "popularity";
|
||||||
_price = (string)e.Value;
|
await LoadThemes();
|
||||||
_search = "";
|
StateHasChanged();
|
||||||
await LoadThemes();
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error On PriceChanged");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Search()
|
private async Task Search()
|
||||||
{
|
{
|
||||||
try
|
await LoadThemes();
|
||||||
{
|
|
||||||
await LoadThemes();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error On Search");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Reset()
|
private async Task Reset()
|
||||||
{
|
{
|
||||||
try
|
_page = 1;
|
||||||
{
|
_search = "";
|
||||||
_search = "";
|
await LoadThemes();
|
||||||
await LoadThemes();
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
private async Task Refresh()
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error On Reset");
|
await LoadThemes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnPageChange(int page)
|
||||||
|
{
|
||||||
|
_page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void SortChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_sort = (string)e.Value;
|
||||||
|
await LoadThemes();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideModal()
|
private void HideModal()
|
||||||
@@ -205,7 +265,7 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var package = await PackageService.GetPackageAsync(packageid, version);
|
var package = await PackageService.GetPackageAsync(packageid, version, false);
|
||||||
if (package != null)
|
if (package != null)
|
||||||
{
|
{
|
||||||
_productname = package.Name;
|
_productname = package.Name;
|
||||||
@@ -215,8 +275,13 @@
|
|||||||
}
|
}
|
||||||
_packageid = package.PackageId;
|
_packageid = package.PackageId;
|
||||||
_version = package.Version;
|
_version = package.Version;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await logger.LogError("Error Getting Package {PackageId} {Version}", packageid, version);
|
||||||
|
AddModuleMessage(Localizer["Error.Theme.Download"], MessageType.Error);
|
||||||
}
|
}
|
||||||
StateHasChanged();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -229,9 +294,9 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
|
await PackageService.DownloadPackageAsync(_packageid, _version);
|
||||||
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
|
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _packageid, _version);
|
||||||
AddModuleMessage(Localizer["Success.Theme.Download"], MessageType.Success);
|
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
_productname = "";
|
_productname = "";
|
||||||
_license = "";
|
_license = "";
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
@@ -243,16 +308,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task InstallThemes()
|
private void OnUpload()
|
||||||
{
|
{
|
||||||
try
|
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
{
|
|
||||||
await ThemeService.InstallThemesAsync();
|
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Installing Theme");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,10 @@
|
|||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
|
if (!NavigationManager.BaseUri.Contains("localhost:"))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
@@ -102,7 +105,8 @@
|
|||||||
{
|
{
|
||||||
if (IsValid(_owner) && IsValid(_theme) && _owner != _theme && _template != "-")
|
if (IsValid(_owner) && IsValid(_theme) && _owner != _theme && _template != "-")
|
||||||
{
|
{
|
||||||
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference };
|
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||||
|
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference, ThemeName = template.Namespace };
|
||||||
theme = await ThemeService.CreateThemeAsync(theme);
|
theme = await ThemeService.CreateThemeAsync(theme);
|
||||||
GetLocation();
|
GetLocation();
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Theme.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
AddModuleMessage(string.Format(Localizer["Success.Theme.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
@@ -121,7 +125,7 @@
|
|||||||
private bool IsValid(string name)
|
private bool IsValid(string name)
|
||||||
{
|
{
|
||||||
// must contain letters, underscores and digits and first character must be letter or underscore
|
// must contain letters, underscores and digits and first character must be letter or underscore
|
||||||
return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
|
return !string.IsNullOrEmpty(name) && name.ToLower() != "theme" && !name.ToLower().Contains("oqtane") && Regex.IsMatch(name, "^[A-Za-z_][A-Za-z0-9_]*$");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TemplateChanged(ChangeEventArgs e)
|
private void TemplateChanged(ChangeEventArgs e)
|
||||||
@@ -142,8 +146,14 @@
|
|||||||
if (_owner != "" && _theme != "" && _template != "-")
|
if (_owner != "" && _theme != "" && _template != "-")
|
||||||
{
|
{
|
||||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||||
_location = template.Location + _owner + "." + _theme;
|
if (!string.IsNullOrEmpty(template.Namespace))
|
||||||
|
{
|
||||||
|
_location = template.Location + template.Namespace.Replace("[Owner]", _owner).Replace("[Theme]", _theme);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_location = template.Location + _owner + ".Theme." + _theme;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
170
Oqtane.Client/Modules/Admin/Themes/Edit.razor
Normal file
170
Oqtane.Client/Modules/Admin/Themes/Edit.razor
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Themes
|
||||||
|
@using System.Net
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject IThemeService ThemeService
|
||||||
|
@inject IPackageService PackageService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
@if (_initialized)
|
||||||
|
{
|
||||||
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="name" class="form-control" @bind="@_name" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="isenabled" HelpText="Is theme enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="isenabled" class="form-select" @bind="@_isenabled" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<Section Name="Information" ResourceKey="Information" Heading="Information">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="themename" class="form-control" @bind="@_themeName" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="version" class="form-control" @bind="@_version" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="owner" class="form-control" @bind="@_owner" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="url" HelpText="The url of the theme" ResourceKey="Url">Url: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="url" class="form-control" @bind="@_url" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="contact" class="form-control" @bind="@_contact" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
|
||||||
|
{
|
||||||
|
<a href="@_license.Replace("~", PageState?.Alias.BaseUrl + "/Themes/" + Utilities.GetTypeName(_themeName))" class="btn btn-info" style="text-decoration: none !important" target="_new">@Localizer["View License"]</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveTheme">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private bool _initialized = false;
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
private int _themeId;
|
||||||
|
private string _themeName = "";
|
||||||
|
private string _isenabled;
|
||||||
|
private string _name;
|
||||||
|
private string _version;
|
||||||
|
private string _packagename = "";
|
||||||
|
private string _owner = "";
|
||||||
|
private string _url = "";
|
||||||
|
private string _contact = "";
|
||||||
|
private string _license = "";
|
||||||
|
private string _createdby;
|
||||||
|
private DateTime _createdon;
|
||||||
|
private string _modifiedby;
|
||||||
|
private DateTime _modifiedon;
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_themeId = Int32.Parse(PageState.QueryString["id"]);
|
||||||
|
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
|
||||||
|
if (theme != null)
|
||||||
|
{
|
||||||
|
_name = theme.Name;
|
||||||
|
_isenabled =theme.IsEnabled.ToString();
|
||||||
|
_version = theme.Version;
|
||||||
|
_packagename = theme.PackageName;
|
||||||
|
_owner = theme.Owner;
|
||||||
|
_url = theme.Url;
|
||||||
|
_contact = theme.Contact;
|
||||||
|
_license = theme.License;
|
||||||
|
_createdby = theme.CreatedBy;
|
||||||
|
_createdon = theme.CreatedOn;
|
||||||
|
_modifiedby = theme.ModifiedBy;
|
||||||
|
_modifiedon = theme.ModifiedOn;
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading Theme {ThemeName} {Error}", _themeName, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Theme.Loading"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveTheme()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId);
|
||||||
|
theme.Name = _name;
|
||||||
|
theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
|
||||||
|
await ThemeService.UpdateThemeAsync(theme);
|
||||||
|
await logger.LogInformation("Theme Saved {Theme}", theme);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Theme {ThemeId} {Error}", _themeId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IThemeService ThemeService
|
@inject IThemeService ThemeService
|
||||||
@inject IPackageService PackageService
|
@inject IPackageService PackageService
|
||||||
|
@inject ISiteService SiteService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@@ -13,29 +14,46 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Install Theme" />
|
<ActionLink Action="Add" Text="Install Theme" ResourceKey="InstallTheme" />
|
||||||
@((MarkupString)" ")
|
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary ps-2" />
|
||||||
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary" />
|
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||||
|
|
||||||
<Pager Items="@_themes">
|
<Pager Items="@_themes">
|
||||||
<Header>
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
<th>@SharedLocalizer["Version"]</th>
|
<th>@SharedLocalizer["Version"]</th>
|
||||||
|
<th>@Localizer["Enabled"]</th>
|
||||||
|
<th>@SharedLocalizer["Support"]</th>
|
||||||
<th>@SharedLocalizer["Expires"]</th>
|
<th>@SharedLocalizer["Expires"]</th>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="View" Parameters="@($"name=" + WebUtility.UrlEncode(context.ThemeName))" ResourceKey="ViewTheme" /></td>
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.ThemeId.ToString())" ResourceKey="EditTheme" /></td>
|
||||||
<td>
|
<td>
|
||||||
@if (context.AssemblyName != "Oqtane.Client")
|
@if (context.AssemblyName != Constants.ClientId)
|
||||||
{
|
{
|
||||||
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
|
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
|
<td><NavLink class="btn btn-secondary" href="@NavigateUrl("admin/site")">@Localizer["Assign"]</NavLink></td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
<td>@context.Version</td>
|
<td>@context.Version</td>
|
||||||
|
<td>
|
||||||
|
@if (context.IsEnabled)
|
||||||
|
{
|
||||||
|
<span>@SharedLocalizer["Yes"]</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>@SharedLocalizer["No"]</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@((MarkupString)SupportLink(context.PackageName, context.Version))
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@((MarkupString)PurchaseLink(context.PackageName))
|
@((MarkupString)PurchaseLink(context.PackageName))
|
||||||
</td>
|
</td>
|
||||||
@@ -48,7 +66,6 @@ else
|
|||||||
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadTheme(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
|
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadTheme(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td></td>
|
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
}
|
}
|
||||||
@@ -64,7 +81,7 @@ else
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_themes = await ThemeService.GetThemesAsync();
|
_themes = await ThemeService.GetThemesAsync();
|
||||||
_packages = await PackageService.GetPackagesAsync("theme");
|
_packages = await PackageService.GetPackageUpdatesAsync("theme");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -86,10 +103,13 @@ else
|
|||||||
{
|
{
|
||||||
if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date)
|
if (package.ExpiryDate != null && package.ExpiryDate.Value.Date != DateTime.MaxValue.Date)
|
||||||
{
|
{
|
||||||
link += "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span>";
|
if (string.IsNullOrEmpty(package.PaymentUrl))
|
||||||
if (!string.IsNullOrEmpty(package.PaymentUrl))
|
|
||||||
{
|
{
|
||||||
link += " <a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + SharedLocalizer["Extend"] + "</a>";
|
link = "<span>" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</span>";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
link = "<a class=\"btn btn-primary\" style=\"text-decoration: none !important\" href=\"" + package.PaymentUrl + "\" target=\"_new\">" + package.ExpiryDate.Value.Date.ToString("MMM dd, yyyy") + "</a>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,6 +117,20 @@ else
|
|||||||
return link;
|
return link;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string SupportLink(string packagename, string version)
|
||||||
|
{
|
||||||
|
string link = "";
|
||||||
|
if (!string.IsNullOrEmpty(packagename) && _packages != null)
|
||||||
|
{
|
||||||
|
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
|
||||||
|
if (package != null && !string.IsNullOrEmpty(package.SupportUrl))
|
||||||
|
{
|
||||||
|
link += "<a class=\"btn btn-info\" style=\"text-decoration: none !important\" href=\"" + package.SupportUrl.Replace("{Version}", version) + "\" target=\"_new\">" + SharedLocalizer["Help"] + "</a>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
private string UpgradeAvailable(string packagename, string version)
|
private string UpgradeAvailable(string packagename, string version)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(packagename) && _packages != null)
|
if (!string.IsNullOrEmpty(packagename) && _packages != null)
|
||||||
@@ -114,9 +148,8 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder);
|
await PackageService.DownloadPackageAsync(packagename, version);
|
||||||
await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", packagename, version);
|
await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", packagename, version);
|
||||||
await ThemeService.InstallThemesAsync();
|
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success);
|
AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -140,4 +173,27 @@ else
|
|||||||
AddModuleMessage(Localizer["Error.Theme.Delete"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Theme.Delete"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task Synchronize()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ShowProgressIndicator();
|
||||||
|
foreach (var theme in _themes)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(theme.PackageName) && !_packages.Any(item => item.PackageId == theme.PackageName))
|
||||||
|
{
|
||||||
|
await PackageService.GetPackageAsync(theme.PackageName, theme.Version, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HideProgressIndicator();
|
||||||
|
AddModuleMessage(Localizer["Success.Theme.Synchronize"], MessageType.Success);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Synchronizing Themes {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Theme.Synchronize"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Themes
|
|
||||||
@using System.Net
|
|
||||||
@inherits ModuleBase
|
|
||||||
@inject IThemeService ThemeService
|
|
||||||
@inject NavigationManager NavigationManager
|
|
||||||
@inject IStringLocalizer<View> Localizer
|
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="name" HelpText="The name of the theme" ResourceKey="Name">Name: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="name" class="form-control" @bind="@_name" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="themename" class="form-control" @bind="@_themeName" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="version" class="form-control" @bind="@_version" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="owner" class="form-control" @bind="@_owner" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="url" class="form-control" @bind="@_url" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="contact" class="form-control" @bind="@_contact" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private string _themeName = "";
|
|
||||||
private string _name;
|
|
||||||
private string _version;
|
|
||||||
private string _owner = "";
|
|
||||||
private string _url = "";
|
|
||||||
private string _contact = "";
|
|
||||||
private string _license = "";
|
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_themeName = WebUtility.UrlDecode(PageState.QueryString["name"]);
|
|
||||||
var themes = await ThemeService.GetThemesAsync();
|
|
||||||
var theme = themes.FirstOrDefault(item => item.ThemeName == _themeName);
|
|
||||||
if (theme != null)
|
|
||||||
{
|
|
||||||
_name = theme.Name;
|
|
||||||
_version = theme.Version;
|
|
||||||
_owner = theme.Owner;
|
|
||||||
_url = theme.Url;
|
|
||||||
_contact = theme.Contact;
|
|
||||||
_license = theme.License;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Loading Theme {ThemeName} {Error}", _themeName, ex.Message);
|
|
||||||
AddModuleMessage(Localizer["Error.Theme.Loading"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,36 +7,68 @@
|
|||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<TabStrip>
|
@if (_initialized)
|
||||||
<TabPanel Name="Download" ResourceKey="Download">
|
{
|
||||||
@if (_package != null && _upgradeavailable)
|
<TabStrip>
|
||||||
{
|
<TabPanel Name="Download" ResourceKey="Download">
|
||||||
<ModuleMessage Type="MessageType.Info" Message="Select The Download Button To Download The Framework Upgrade Package And Then Select Upgrade"></ModuleMessage>
|
@if (_package != null && _upgradeavailable)
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@SharedLocalizer["Download"] @_package.Version</button>
|
{
|
||||||
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
|
<div class="container">
|
||||||
}
|
<div class="row mb-1 align-items-center">
|
||||||
else
|
<Label Class="col-sm-3" HelpText="Specify if you want to backup files during the upgrade process. Disabling this option will reduce the time required for the upgrade." ResourceKey="Backup">Backup Files? </Label>
|
||||||
{
|
<div class="col-sm-9">
|
||||||
<ModuleMessage Type="MessageType.Info" Message="Framework Is Already Up To Date"></ModuleMessage>
|
<select id="backup" class="form-select" @bind="@_backup">
|
||||||
}
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
</TabPanel>
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
<TabPanel Name="Upload" ResourceKey="Upload">
|
</select>
|
||||||
<ModuleMessage Type="MessageType.Info" Message="Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade"></ModuleMessage>
|
</div>
|
||||||
<div class="container">
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
</div>
|
||||||
<Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
|
<br />
|
||||||
<div class="col-sm-9">
|
@if (!_downloaded)
|
||||||
<FileManager Folder="@Constants.PackagesFolder" />
|
{
|
||||||
|
<button type="button" class="btn btn-primary" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@SharedLocalizer["Download"] @_package.Version</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<ModuleMessage Type="MessageType.Info" Message=@Localizer["Message.Text"]></ModuleMessage>
|
||||||
|
}
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Upload" ResourceKey="Upload">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<FileManager Folder="@Constants.PackagesFolder" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" HelpText="Specify if you want to backup files during the upgrade process. Disabling this option will reduce the time required for the upgrade." ResourceKey="Backup">Backup Files? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="backup" class="form-select" @bind="@_backup">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
|
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private bool _initialized = false;
|
||||||
|
private bool _downloaded = false;
|
||||||
private Package _package;
|
private Package _package;
|
||||||
private bool _upgradeavailable = false;
|
private bool _upgradeavailable = false;
|
||||||
|
private string _backup = "True";
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
@@ -44,18 +76,28 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
|
if (NavigationManager.BaseUri.Contains("localhost:"))
|
||||||
if (packages != null)
|
|
||||||
{
|
{
|
||||||
_package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault();
|
AddModuleMessage(Localizer["Localhost.Text"], MessageType.Info);
|
||||||
if (_package != null)
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Disclaimer.Text"], MessageType.Warning);
|
||||||
|
|
||||||
|
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
|
||||||
|
if (packages != null)
|
||||||
{
|
{
|
||||||
_upgradeavailable = (Version.Parse(_package.Version).CompareTo(Version.Parse(Constants.Version)) > 0);
|
_package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault();
|
||||||
}
|
if (_package != null)
|
||||||
else
|
{
|
||||||
{
|
_upgradeavailable = (Version.Parse(_package.Version).CompareTo(Version.Parse(Constants.Version)) > 0);
|
||||||
_package = new Package { Name = Constants.PackageId, Version = Constants.Version };
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_package = new Package { Name = Constants.PackageId, Version = Constants.Version };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -72,7 +114,7 @@
|
|||||||
ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
await interop.RedirectBrowser(NavigateUrl(), 10);
|
await interop.RedirectBrowser(NavigateUrl(), 10);
|
||||||
await InstallationService.Upgrade();
|
await InstallationService.Upgrade(bool.Parse(_backup));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -85,13 +127,17 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PackageService.DownloadPackageAsync(packageid, version, Constants.PackagesFolder);
|
ShowProgressIndicator();
|
||||||
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version, Constants.PackagesFolder);
|
await PackageService.DownloadPackageAsync(packageid, version);
|
||||||
|
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version);
|
||||||
|
_downloaded = true;
|
||||||
|
HideProgressIndicator();
|
||||||
AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Downloading Framework Package {Error}", ex.Message);
|
await logger.LogError(ex, "Error Downloading Framework Package {Error}", ex.Message);
|
||||||
|
HideProgressIndicator();
|
||||||
AddModuleMessage(Localizer["Error.Framework.Download"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Framework.Download"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,16 @@
|
|||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="url" HelpText="The fully qualified Url for this site" ResourceKey="Url">Url:</Label>
|
<Label Class="col-sm-3" For="url" HelpText="A Url identifying a path to a specific page in the site (absolute or relative)" ResourceKey="Url">Url:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" required />
|
<div class="input-group">
|
||||||
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" required />
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="GenerateUrl">@Localizer["Generate"]</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="mappedurl" HelpText="A fully qualified Url where the user will be redirected" ResourceKey="MappedUrl">Redirect To:</Label>
|
<Label Class="col-sm-3" For="mappedurl" HelpText="A Url where the user will be redirected (absolute or relative). Use '/' for site root path." ResourceKey="MappedUrl">Redirect To:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
||||||
</div>
|
</div>
|
||||||
@@ -26,64 +29,80 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private ElementReference form;
|
private ElementReference form;
|
||||||
private bool validated = false;
|
private bool validated = false;
|
||||||
|
|
||||||
private string _url = string.Empty;
|
private string _url = string.Empty;
|
||||||
private string _mappedurl = string.Empty;
|
private string _mappedurl = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
private async Task SaveUrlMapping()
|
private async Task SaveUrlMapping()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
if (_url != _mappedurl)
|
if (_url != _mappedurl)
|
||||||
{
|
{
|
||||||
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
|
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
|
||||||
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
|
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
|
||||||
|
|
||||||
_url = (_url.StartsWith("/")) ? _url.Substring(1) : _url;
|
_url = (_url.StartsWith("/")) ? _url.Substring(1) : _url;
|
||||||
_url = (!_url.StartsWith("http")) ? url + _url : _url;
|
_url = (!_url.StartsWith("http")) ? url + _url : _url;
|
||||||
|
|
||||||
if (_url.StartsWith(url))
|
_mappedurl = _mappedurl.Replace(url, "");
|
||||||
{
|
_mappedurl = (_mappedurl.StartsWith("/") && _mappedurl != "/") ? _mappedurl.Substring(1) : _mappedurl;
|
||||||
var urlmapping = new UrlMapping();
|
|
||||||
urlmapping.SiteId = PageState.Site.SiteId;
|
|
||||||
var route = new Route(_url, PageState.Alias.Path);
|
|
||||||
urlmapping.Url = route.PagePath;
|
|
||||||
urlmapping.MappedUrl = _mappedurl.Replace(url, "");
|
|
||||||
urlmapping.Requests = 0;
|
|
||||||
urlmapping.CreatedOn = DateTime.UtcNow;
|
|
||||||
urlmapping.RequestedOn = DateTime.UtcNow;
|
|
||||||
|
|
||||||
try
|
if (_url.StartsWith(url))
|
||||||
{
|
{
|
||||||
urlmapping = await UrlMappingService.AddUrlMappingAsync(urlmapping);
|
var urlmapping = new UrlMapping();
|
||||||
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
|
urlmapping.SiteId = PageState.Site.SiteId;
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
urlmapping.Url = new Route(_url, PageState.Alias.Path).PagePath;
|
||||||
}
|
urlmapping.MappedUrl = _mappedurl;
|
||||||
catch (Exception ex)
|
urlmapping.Requests = 0;
|
||||||
{
|
urlmapping.CreatedOn = DateTime.UtcNow;
|
||||||
await logger.LogError(ex, "Error Saving UrlMapping {UrlMapping} {Error}", urlmapping, ex.Message);
|
urlmapping.RequestedOn = DateTime.UtcNow;
|
||||||
AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error);
|
|
||||||
}
|
try
|
||||||
}
|
{
|
||||||
else
|
urlmapping = await UrlMappingService.AddUrlMappingAsync(urlmapping);
|
||||||
{
|
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
|
||||||
AddModuleMessage(Localizer["Message.SaveUrlMapping"], MessageType.Warning);
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
}
|
catch (Exception ex)
|
||||||
else
|
{
|
||||||
{
|
await logger.LogError(ex, "Error Saving UrlMapping {UrlMapping} {Error}", urlmapping, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.SaveUrlMapping"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
AddModuleMessage(Localizer["Message.DuplicateUrlMapping"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.DuplicateUrlMapping"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GenerateUrl()
|
||||||
|
{
|
||||||
|
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
|
||||||
|
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
|
||||||
|
|
||||||
|
var chars = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
Random rnd = new Random();
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
url += chars.Substring(rnd.Next(0, chars.Length - 1), 1);
|
||||||
|
}
|
||||||
|
_url = url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="url" HelpText="A fully qualified Url for this site" ResourceKey="Url">Url:</Label>
|
<Label Class="col-sm-3" For="url" HelpText="A Url identifying a path to a specific page in the site (absolute or relative)" ResourceKey="Url">Url:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" readonly />
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="mappedurl" HelpText="A fully qualified Url where the user will be redirected" ResourceKey="MappedUrl">Redirect To:</Label>
|
<Label Class="col-sm-3" For="mappedurl" HelpText="A Url where the user will be redirected (absolute or relative). Use '/' for site root path." ResourceKey="MappedUrl">Redirect To:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
||||||
</div>
|
</div>
|
||||||
@@ -67,8 +67,11 @@
|
|||||||
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
|
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
|
||||||
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
|
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
|
||||||
|
|
||||||
|
_mappedurl = _mappedurl.Replace(url, "");
|
||||||
|
_mappedurl = (_mappedurl.StartsWith("/") && _mappedurl != "/") ? _mappedurl.Substring(1) : _mappedurl;
|
||||||
|
|
||||||
var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid);
|
var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid);
|
||||||
urlmapping.MappedUrl = _mappedurl.Replace(url, "");
|
urlmapping.MappedUrl = _mappedurl;
|
||||||
urlmapping = await UrlMappingService.UpdateUrlMappingAsync(urlmapping);
|
urlmapping = await UrlMappingService.UpdateUrlMappingAsync(urlmapping);
|
||||||
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
|
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUrlMappingService UrlMappingService
|
@inject IUrlMappingService UrlMappingService
|
||||||
@inject ISiteService SiteService
|
@inject ISiteService SiteService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
<Pager Items="@_urlMappings">
|
<Pager Items="@_urlMappings" SearchProperties="Url">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -37,7 +38,7 @@ else
|
|||||||
<th>@Localizer["Requested"]</th>
|
<th>@Localizer["Requested"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
||||||
<td><ActionDialog Header="Delete Url Mapping" Message="@string.Format(Localizer["Confirm.DeleteUrlMapping"], context.Url)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUrlMapping(context))" ResourceKey="DeleteUrlMapping" /></td>
|
<td><ActionDialog Header="Delete Url Mapping" Message="@string.Format(Localizer["Confirm.DeleteUrlMapping"], context.Url)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUrlMapping(context))" ResourceKey="DeleteUrlMapping" /></td>
|
||||||
<td>
|
<td>
|
||||||
<a href="@Utilities.TenantUrl(PageState.Alias, context.Url)">@context.Url</a>
|
<a href="@Utilities.TenantUrl(PageState.Alias, context.Url)">@context.Url</a>
|
||||||
@@ -47,7 +48,7 @@ else
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>@context.Requests</td>
|
<td>@context.Requests</td>
|
||||||
<td>@context.RequestedOn</td>
|
<td>@UtcToLocal(context.RequestedOn)</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
@@ -62,7 +63,13 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="retention" HelpText="Number of days of broken urls to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
@@ -73,6 +80,7 @@ else
|
|||||||
private bool _mapped = true;
|
private bool _mapped = true;
|
||||||
private List<UrlMapping> _urlMappings;
|
private List<UrlMapping> _urlMappings;
|
||||||
private string _capturebrokenurls;
|
private string _capturebrokenurls;
|
||||||
|
private int _retention = 30;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
@@ -80,7 +88,10 @@ else
|
|||||||
{
|
{
|
||||||
await GetUrlMappings();
|
await GetUrlMappings();
|
||||||
_capturebrokenurls = PageState.Site.CaptureBrokenUrls.ToString();
|
_capturebrokenurls = PageState.Site.CaptureBrokenUrls.ToString();
|
||||||
}
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_retention = int.Parse(SettingService.GetSetting(settings, "UrlMappingRetention", "30"));
|
||||||
|
}
|
||||||
|
|
||||||
private async void MappedChanged(ChangeEventArgs e)
|
private async void MappedChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -124,7 +135,12 @@ else
|
|||||||
var site = PageState.Site;
|
var site = PageState.Site;
|
||||||
site.CaptureBrokenUrls = bool.Parse(_capturebrokenurls);
|
site.CaptureBrokenUrls = bool.Parse(_capturebrokenurls);
|
||||||
await SiteService.UpdateSiteAsync(site);
|
await SiteService.UpdateSiteAsync(site);
|
||||||
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
|
@inject IUserRoleService UserRoleService
|
||||||
@inject INotificationService NotificationService
|
@inject INotificationService NotificationService
|
||||||
@inject IStringLocalizer<Add> Localizer
|
@inject IStringLocalizer<Add> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
@@ -10,9 +11,9 @@
|
|||||||
{
|
{
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="to" HelpText="Enter the username you wish to send a message to" ResourceKey="To">To: </Label>
|
<Label Class="col-sm-3" For="to" HelpText="Enter the user you wish to send a message to" ResourceKey="To">To: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="to" class="form-control" @bind="@username" />
|
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="username" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -30,11 +31,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<br/>
|
<br/>
|
||||||
<button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
|
<button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string username = "";
|
private AutoComplete username;
|
||||||
private string subject = "";
|
private string subject = "";
|
||||||
private string body = "";
|
private string body = "";
|
||||||
|
|
||||||
@@ -42,21 +43,35 @@
|
|||||||
|
|
||||||
public override string Title => "Send Notification";
|
public override string Title => "Send Notification";
|
||||||
|
|
||||||
|
private async Task<Dictionary<string, string>> GetUsers(string filter)
|
||||||
|
{
|
||||||
|
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||||
|
return users.Where(item => item.User.Username.Contains(filter, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToDictionary(item => item.UserId.ToString(), item => item.User.Username);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task Send()
|
private async Task Send()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
|
if (!string.IsNullOrEmpty(username.Key) && !string.IsNullOrEmpty(subject))
|
||||||
if (user != null)
|
|
||||||
{
|
{
|
||||||
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body);
|
var user = await UserService.GetUserAsync(int.Parse(username.Key), ModuleState.SiteId);
|
||||||
notification = await NotificationService.AddNotificationAsync(notification);
|
if (user != null)
|
||||||
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body);
|
||||||
|
notification = await NotificationService.AddNotificationAsync(notification);
|
||||||
|
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
|
||||||
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Required"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
@namespace Oqtane.Modules.Admin.UserProfile
|
@namespace Oqtane.Modules.Admin.UserProfile
|
||||||
|
@using System.Net
|
||||||
|
@using System.Text.RegularExpressions;
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
@@ -7,127 +9,210 @@
|
|||||||
@inject INotificationService NotificationService
|
@inject INotificationService NotificationService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
@inject IJSRuntime jsRuntime
|
||||||
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (PageState.User != null && photo != null)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
<img src="@ImageUrl(photofileid, 400, 400)" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
|
@if (PageState.User != null && _photo != null)
|
||||||
}
|
{
|
||||||
else
|
<img src="@ImageUrl(_photofileid, 400, 400)" alt="@_displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
|
||||||
{
|
}
|
||||||
<br />
|
else
|
||||||
}
|
{
|
||||||
<TabStrip>
|
<br />
|
||||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
}
|
||||||
@if (profiles != null && settings != null)
|
<TabStrip>
|
||||||
{
|
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||||
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
|
<Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="username" class="form-control" @bind="@username" readonly />
|
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
|
<Label Class="col-sm-3" For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
|
<Label Class="col-sm-3" For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (allowtwofactor)
|
@if (_allowtwofactor)
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="twofactor" HelpText="Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in." ResourceKey="TwoFactor"></Label>
|
<Label Class="col-sm-3" For="twofactor" HelpText="Indicates if you are using two factor authentication. Two factor authentication requires you to enter a verification code sent via email after you sign in." ResourceKey="TwoFactor"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="twofactor" class="form-select" @bind="@twofactor" required>
|
<select id="twofactor" class="form-select" @bind="@_twofactor" required>
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="email" class="form-control" @bind="@email" />
|
<input id="email" class="form-control" @bind="@_email" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
<input id="displayname" class="form-control" @bind="@_displayname" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="@photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
|
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<FileManager FileId="@photofileid" Filter="@Constants.ImageFiles" ShowFolders="false" ShowFiles="true" UploadMultiple="false" FolderId="@folderid" @ref="filemanager" />
|
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var timezone in _timezones)
|
||||||
|
{
|
||||||
|
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="@_photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<FileManager FileId="@_photofileid" Filter="@PageState.Site.ImageFiles" ShowFolders="false" ShowFiles="true" UploadMultiple="false" FolderId="@_folderid" @ref="_filemanager" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
}
|
<button type="button" class="btn btn-danger" @onclick="Logout">@Localizer["Logout Everywhere"]</button>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||||
@if (profiles != null && settings != null)
|
|
||||||
{
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@foreach (Profile profile in profiles)
|
@foreach (Profile profile in _profiles)
|
||||||
{
|
{
|
||||||
var p = profile;
|
var p = profile;
|
||||||
if (!p.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
if (!p.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
{
|
{
|
||||||
if (p.Category != category)
|
if (p.Category != _category)
|
||||||
{
|
{
|
||||||
<div class="col text-center pb-2">
|
<div class="col text-center pb-2">
|
||||||
@p.Category
|
@p.Category
|
||||||
</div>
|
</div>
|
||||||
category = p.Category;
|
_category = p.Category;
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@if (!string.IsNullOrEmpty(p.Options))
|
@if (!string.IsNullOrEmpty(p.Options))
|
||||||
{
|
{
|
||||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
@if (!string.IsNullOrEmpty(p.Autocomplete))
|
||||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
|
||||||
{
|
|
||||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
|
||||||
{
|
|
||||||
<option value="@option" selected>@option</option>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<option value="@option">@option</option>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@if (p.IsRequired)
|
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete">
|
||||||
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||||
|
{
|
||||||
|
<option value="@option" selected>@option</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<option value="@option">@option</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||||
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||||
|
{
|
||||||
|
<option value="@option" selected>@option</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<option value="@option">@option</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (p.Rows == 1)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(p.Autocomplete))
|
||||||
|
{
|
||||||
|
@if (p.IsRequired)
|
||||||
|
{
|
||||||
|
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (p.IsRequired)
|
||||||
|
{
|
||||||
|
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(p.Autocomplete))
|
||||||
|
{
|
||||||
|
@if (p.IsRequired)
|
||||||
|
{
|
||||||
|
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (p.IsRequired)
|
||||||
|
{
|
||||||
|
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))"
|
||||||
|
@attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -138,153 +223,228 @@ else
|
|||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
}
|
</TabPanel>
|
||||||
</TabPanel>
|
<TabPanel Name="Notifications" ResourceKey="Notifications">
|
||||||
<TabPanel Name="Notifications" ResourceKey="Notifications">
|
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" />
|
||||||
@if (notifications != null)
|
<br />
|
||||||
{
|
|
||||||
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" />
|
|
||||||
<br /><br />
|
|
||||||
@if (filter == "to")
|
|
||||||
{
|
|
||||||
<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>
|
|
||||||
</Header>
|
|
||||||
<Row>
|
|
||||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></td>
|
|
||||||
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
|
||||||
<td>@context.FromDisplayName</td>
|
|
||||||
<td>@context.Subject</td>
|
|
||||||
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
|
|
||||||
</Row>
|
|
||||||
<Detail>
|
|
||||||
<td colspan="2"></td>
|
|
||||||
<td colspan="3">
|
|
||||||
@{
|
|
||||||
string input = "___";
|
|
||||||
if (context.Body.Contains(input))
|
|
||||||
{
|
|
||||||
context.Body = context.Body.Split(input)[0];
|
|
||||||
context.Body = context.Body.Replace("\n", "");
|
|
||||||
context.Body = context.Body.Replace("\r", "");
|
|
||||||
} }
|
|
||||||
@(context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body)
|
|
||||||
</td>
|
|
||||||
</Detail>
|
|
||||||
</Pager>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<Pager Items="@notifications">
|
|
||||||
<Header>
|
|
||||||
<th> </th>
|
|
||||||
<th> </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" /></td>
|
|
||||||
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
|
||||||
<td>@context.ToDisplayName</td>
|
|
||||||
<td>@context.Subject</td>
|
|
||||||
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
|
|
||||||
</Row>
|
|
||||||
<Detail>
|
|
||||||
<td colspan="2"></td>
|
|
||||||
<td colspan="3">
|
|
||||||
@{
|
|
||||||
string input = "___";
|
|
||||||
if (context.Body.Contains(input))
|
|
||||||
{
|
|
||||||
context.Body = context.Body.Split(input)[0];
|
|
||||||
context.Body = context.Body.Replace("\n", "");
|
|
||||||
context.Body = context.Body.Replace("\r", "");
|
|
||||||
} }
|
|
||||||
@(context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body)
|
|
||||||
</td>
|
|
||||||
</Detail>
|
|
||||||
</Pager>
|
|
||||||
}
|
|
||||||
<br />
|
<br />
|
||||||
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
|
|
||||||
<br /><hr />
|
|
||||||
<select class="form-select" @onchange="(e => FilterChanged(e))">
|
<select class="form-select" @onchange="(e => FilterChanged(e))">
|
||||||
<option value="to">@Localizer["Inbox"]</option>
|
<option value="to">@Localizer["Inbox"]</option>
|
||||||
<option value="from">@Localizer["Items.Sent"]</option>
|
<option value="from">@Localizer["Items.Sent"]</option>
|
||||||
</select>
|
</select>
|
||||||
}
|
<br />
|
||||||
</TabPanel>
|
@if (_filter == "to")
|
||||||
</TabStrip>
|
{
|
||||||
<br /><br />
|
@if (_notifications.Any())
|
||||||
|
{
|
||||||
|
<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>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||||
|
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||||
|
|
||||||
|
@if (context.IsRead)
|
||||||
|
{
|
||||||
|
<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>@(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>
|
||||||
|
}
|
||||||
|
</Row>
|
||||||
|
<Detail>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
<td colspan="3">
|
||||||
|
@{
|
||||||
|
string input = "___";
|
||||||
|
if (context.Body.Contains(input))
|
||||||
|
{
|
||||||
|
context.Body = context.Body.Split(input)[0];
|
||||||
|
context.Body = context.Body.Replace("\n", "");
|
||||||
|
context.Body = context.Body.Replace("\r", "");
|
||||||
|
}
|
||||||
|
_notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
|
||||||
|
}
|
||||||
|
@if (context.IsRead)
|
||||||
|
{
|
||||||
|
@_notificationSummary
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<b>@_notificationSummary</b>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</Detail>
|
||||||
|
</Pager>
|
||||||
|
<br />
|
||||||
|
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="no-notifications-text">
|
||||||
|
@Localizer["NoNotificationsReceived.Text"]
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (_notifications.Any())
|
||||||
|
{
|
||||||
|
<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>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||||
|
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||||
|
|
||||||
|
@if (context.IsRead)
|
||||||
|
{
|
||||||
|
<td>@context.ToDisplayName</td>
|
||||||
|
<td>@context.Subject</td>
|
||||||
|
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</td>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<td><b>@context.ToDisplayName</b></td>
|
||||||
|
<td><b>@context.Subject</b></td>
|
||||||
|
<td><b>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", @context.CreatedOn)</b></td>
|
||||||
|
}
|
||||||
|
|
||||||
|
</Row>
|
||||||
|
<Detail>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
<td colspan="3">
|
||||||
|
@{
|
||||||
|
string input = "___";
|
||||||
|
if (context.Body.Contains(input))
|
||||||
|
{
|
||||||
|
context.Body = context.Body.Split(input)[0];
|
||||||
|
context.Body = context.Body.Replace("\n", "");
|
||||||
|
context.Body = context.Body.Replace("\r", "");
|
||||||
|
}
|
||||||
|
_notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
|
||||||
|
}
|
||||||
|
@if (context.IsRead)
|
||||||
|
{
|
||||||
|
@_notificationSummary
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<b>@_notificationSummary</b>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</Detail>
|
||||||
|
</Pager>
|
||||||
|
<br />
|
||||||
|
<ActionDialog Header="Clear Notifications" Message="Are You Sure You Wish To Permanently Delete All Notifications ?" Action="Delete All Notifications" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllNotifications())" ResourceKey="DeleteAllNotifications" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="no-notifications-text">
|
||||||
|
@Localizer["NoNotificationsSent.Text"]
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string username = string.Empty;
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private bool _initialized = false;
|
||||||
|
private string _passwordrequirements;
|
||||||
|
private string _username = string.Empty;
|
||||||
private string _password = string.Empty;
|
private string _password = string.Empty;
|
||||||
private string _passwordtype = "password";
|
private string _passwordtype = "password";
|
||||||
private string _togglepassword = string.Empty;
|
private string _togglepassword = string.Empty;
|
||||||
private string confirm = string.Empty;
|
private string _confirm = string.Empty;
|
||||||
private bool allowtwofactor = false;
|
private bool _allowtwofactor = false;
|
||||||
private string twofactor = "False";
|
private string _twofactor = "False";
|
||||||
private string email = string.Empty;
|
private string _email = string.Empty;
|
||||||
private string displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private FileManager filemanager;
|
private FileManager _filemanager;
|
||||||
private int folderid = -1;
|
private int _folderid = -1;
|
||||||
private int photofileid = -1;
|
private string _timezoneid = string.Empty;
|
||||||
private File photo = null;
|
private int _photofileid = -1;
|
||||||
private List<Profile> profiles;
|
private File _photo = null;
|
||||||
private Dictionary<string, string> settings;
|
private string _imagefiles = string.Empty;
|
||||||
private string category = string.Empty;
|
|
||||||
private string filter = "to";
|
private List<Profile> _profiles;
|
||||||
private List<Notification> notifications;
|
private Dictionary<string, string> _userSettings;
|
||||||
|
private string _category = string.Empty;
|
||||||
|
|
||||||
|
private string _filter = "to";
|
||||||
|
private List<Notification> _notifications;
|
||||||
|
private string _notificationSummary = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
_allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
|
||||||
if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"]))
|
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||||
{
|
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||||
allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PageState.User != null)
|
if (PageState.User != null)
|
||||||
{
|
{
|
||||||
username = PageState.User.Username;
|
_username = PageState.User.Username;
|
||||||
twofactor = PageState.User.TwoFactorRequired.ToString();
|
_twofactor = PageState.User.TwoFactorRequired.ToString();
|
||||||
email = PageState.User.Email;
|
_email = PageState.User.Email;
|
||||||
displayname = PageState.User.DisplayName;
|
_displayname = PageState.User.DisplayName;
|
||||||
|
_timezoneid = PageState.User.TimeZoneId;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_email))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.User.NoEmail"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
// get user folder
|
// get user folder
|
||||||
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
|
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
|
||||||
if (folder != null)
|
if (folder != null)
|
||||||
{
|
{
|
||||||
folderid = folder.FolderId;
|
_folderid = folder.FolderId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PageState.User.PhotoFileId != null)
|
if (PageState.User.PhotoFileId != null)
|
||||||
{
|
{
|
||||||
photofileid = PageState.User.PhotoFileId.Value;
|
_photofileid = PageState.User.PhotoFileId.Value;
|
||||||
photo = await FileService.GetFileAsync(photofileid);
|
_photo = await FileService.GetFileAsync(_photofileid);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
photofileid = -1;
|
_photofileid = -1;
|
||||||
photo = null;
|
_photo = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
_userSettings = PageState.User.Settings;
|
||||||
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_imagefiles = SettingService.GetSetting(_userSettings, "ImageFiles", Constants.ImageFiles);
|
||||||
|
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
|
||||||
|
|
||||||
await LoadNotificationsAsync();
|
await LoadNotificationsAsync();
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -300,49 +460,74 @@ else
|
|||||||
|
|
||||||
private async Task LoadNotificationsAsync()
|
private async Task LoadNotificationsAsync()
|
||||||
{
|
{
|
||||||
notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId);
|
_notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, _filter, PageState.User.UserId);
|
||||||
notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
|
_notifications = _notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||||
=> SettingService.GetSetting(settings, SettingName, DefaultValue);
|
{
|
||||||
|
string value = SettingService.GetSetting(_userSettings, SettingName, DefaultValue);
|
||||||
|
if (value.Contains("]"))
|
||||||
|
{
|
||||||
|
value = value.Substring(value.IndexOf("]") + 1);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task Save()
|
private async Task Save()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (username != string.Empty && email != string.Empty && ValidateProfiles())
|
if (_username != string.Empty && _email != string.Empty)
|
||||||
{
|
{
|
||||||
if (_password == confirm)
|
if (_password == _confirm)
|
||||||
{
|
{
|
||||||
var user = PageState.User;
|
if (ValidateProfiles())
|
||||||
user.Username = username;
|
|
||||||
user.Password = _password;
|
|
||||||
user.TwoFactorRequired = bool.Parse(twofactor);
|
|
||||||
user.Email = email;
|
|
||||||
user.DisplayName = (displayname == string.Empty ? username : displayname);
|
|
||||||
user.PhotoFileId = filemanager.GetFileId();
|
|
||||||
if (user.PhotoFileId == -1)
|
|
||||||
{
|
{
|
||||||
user.PhotoFileId = null;
|
var user = PageState.User;
|
||||||
}
|
user.Username = _username;
|
||||||
if (user.PhotoFileId != null)
|
user.Password = _password;
|
||||||
{
|
user.TwoFactorRequired = bool.Parse(_twofactor);
|
||||||
photofileid = user.PhotoFileId.Value;
|
user.Email = _email;
|
||||||
photo = await FileService.GetFileAsync(photofileid);
|
user.DisplayName = (_displayname == string.Empty ? _username : _displayname);
|
||||||
}
|
user.TimeZoneId = _timezoneid;
|
||||||
else
|
user.PhotoFileId = _filemanager.GetFileId();
|
||||||
{
|
if (user.PhotoFileId == -1)
|
||||||
photofileid = -1;
|
{
|
||||||
photo = null;
|
user.PhotoFileId = null;
|
||||||
}
|
}
|
||||||
|
if (user.PhotoFileId != null)
|
||||||
|
{
|
||||||
|
_photofileid = user.PhotoFileId.Value;
|
||||||
|
_photo = await FileService.GetFileAsync(_photofileid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_photofileid = -1;
|
||||||
|
_photo = null;
|
||||||
|
}
|
||||||
|
|
||||||
await UserService.UpdateUserAsync(user);
|
user = await UserService.UpdateUserAsync(user);
|
||||||
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
|
if (user != null)
|
||||||
await logger.LogInformation("User Profile Saved");
|
{
|
||||||
|
await SettingService.UpdateUserSettingsAsync(_userSettings, PageState.User.UserId);
|
||||||
|
await logger.LogInformation("User Profile Saved");
|
||||||
|
|
||||||
AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success);
|
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||||
StateHasChanged();
|
{
|
||||||
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
|
}
|
||||||
|
else // legacy behavior
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -353,6 +538,8 @@ else
|
|||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Required.ProfileInfo"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Required.ProfileInfo"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ScrollToPageTop();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -361,35 +548,72 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task Logout()
|
||||||
|
{
|
||||||
|
await logger.LogInformation("User Logout Everywhere For Username {Username}", PageState.User?.Username);
|
||||||
|
|
||||||
|
var url = NavigateUrl(""); // home page
|
||||||
|
|
||||||
|
if (PageState.Runtime == Shared.Runtime.Hybrid)
|
||||||
|
{
|
||||||
|
if (PageState.User != null)
|
||||||
|
{
|
||||||
|
// hybrid apps utilize an interactive logout
|
||||||
|
await UserService.LogoutUserEverywhereAsync(PageState.User);
|
||||||
|
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||||
|
authstateprovider.NotifyAuthenticationChanged();
|
||||||
|
NavigationManager.NavigateTo(url, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// post to the Logout page to complete the logout process
|
||||||
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = url, everywhere = true };
|
||||||
|
var interop = new Interop(jsRuntime);
|
||||||
|
await interop.SubmitForm(Utilities.TenantUrl(PageState.Alias, "/pages/logout/"), fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool ValidateProfiles()
|
private bool ValidateProfiles()
|
||||||
{
|
{
|
||||||
bool valid = true;
|
foreach (Profile profile in _profiles)
|
||||||
foreach (Profile profile in profiles)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue))
|
var value = GetProfileValue(profile.Name, string.Empty);
|
||||||
|
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||||
{
|
{
|
||||||
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
|
_userSettings = SettingService.SetSetting(_userSettings, profile.Name, profile.DefaultValue);
|
||||||
}
|
}
|
||||||
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
{
|
{
|
||||||
if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
|
if (profile.IsRequired && string.IsNullOrEmpty(value))
|
||||||
{
|
{
|
||||||
valid = false;
|
AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(profile.Validation))
|
||||||
|
{
|
||||||
|
Regex regex = new Regex(profile.Validation);
|
||||||
|
bool valid = regex.Match(value).Success;
|
||||||
|
if (!valid)
|
||||||
|
{
|
||||||
|
AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return valid;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Cancel()
|
private void Cancel()
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||||
{
|
{
|
||||||
var value = (string)e.Value;
|
var value = (string)e.Value;
|
||||||
settings = SettingService.SetSetting(settings, SettingName, value);
|
_userSettings = SettingService.SetSetting(_userSettings, SettingName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Delete(Notification Notification)
|
private async Task Delete(Notification Notification)
|
||||||
@@ -419,8 +643,7 @@ else
|
|||||||
|
|
||||||
private async void FilterChanged(ChangeEventArgs e)
|
private async void FilterChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
filter = (string)e.Value;
|
_filter = (string)e.Value;
|
||||||
|
|
||||||
await LoadNotificationsAsync();
|
await LoadNotificationsAsync();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
@@ -429,7 +652,8 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach(var Notification in notifications)
|
ShowProgressIndicator();
|
||||||
|
foreach(var Notification in _notifications)
|
||||||
{
|
{
|
||||||
if (!Notification.IsDeleted)
|
if (!Notification.IsDeleted)
|
||||||
{
|
{
|
||||||
@@ -444,28 +668,29 @@ else
|
|||||||
}
|
}
|
||||||
await logger.LogInformation("Notifications Permanently Deleted");
|
await logger.LogInformation("Notifications Permanently Deleted");
|
||||||
await LoadNotificationsAsync();
|
await LoadNotificationsAsync();
|
||||||
|
HideProgressIndicator();
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Deleting Notifications {Error}", ex.Message);
|
await logger.LogError(ex, "Error Deleting Notifications {Error}", ex.Message);
|
||||||
AddModuleMessage(ex.Message, MessageType.Error);
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TogglePassword()
|
private void TogglePassword()
|
||||||
{
|
{
|
||||||
if (_passwordtype == "password")
|
if (_passwordtype == "password")
|
||||||
{
|
{
|
||||||
_passwordtype = "text";
|
_passwordtype = "text";
|
||||||
_togglepassword = SharedLocalizer["HidePassword"];
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_passwordtype = "password";
|
_passwordtype = "password";
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,94 +8,71 @@
|
|||||||
|
|
||||||
@if (PageState.User != null)
|
@if (PageState.User != null)
|
||||||
{
|
{
|
||||||
<div class="container">
|
@if (title == "From")
|
||||||
<div class="row mb-1 align-items-center">
|
{
|
||||||
<label Class="col-sm-3">@Localizer["Title"] </label>
|
<div class="container">
|
||||||
@if (title == "From")
|
|
||||||
{
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<input class="form-control" @bind="@username" readonly />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (title == "To")
|
|
||||||
{
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<input class="form-control" @bind="@username" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<label Class="col-sm-3">@Localizer["Subject"] </label>
|
|
||||||
@if (title == "From")
|
|
||||||
{
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<input class="form-control" @bind="@subject" readonly />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (title == "To")
|
|
||||||
{
|
|
||||||
<div class="col-sm-3">
|
|
||||||
<input class="form-control" @bind="@subject" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container">
|
|
||||||
@if (title == "From")
|
|
||||||
{
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<label class="col-sm-3">@Localizer["Date"] </label>
|
<Label Class="col-sm-3" For="username" HelpText="The user who sent the message" ResourceKey="From">From:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input class="form-control" @bind="@createdon" readonly />
|
<input id="username" class="form-control" @bind="@username" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
<div class="row mb-1 align-items-center">
|
||||||
@if (title == "From")
|
<Label Class="col-sm-3" For="subject" HelpText="The subject of the message" ResourceKey="Subject">Subject:</Label>
|
||||||
{
|
<div class="col-sm-9">
|
||||||
<div class="row mb-1 align-items-center">
|
<input id="subject" class="form-control" @bind="@subject" readonly />
|
||||||
<label class="col-sm-3">@Localizer["Message"] </label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<textarea class="form-control" @bind="@body" rows="5" readonly />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
}
|
<div class="row mb-1 align-items-center">
|
||||||
@if (title == "To")
|
<Label class="col-sm-3" For="date" HelpText="The date the message was sent" ResourceKey="Date">Sent:</Label>
|
||||||
{
|
<div class="col-sm-9">
|
||||||
|
<input id="date" class="form-control" @bind="@createdon" readonly />
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<label class="col-sm-3">@Localizer["Message"] </label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<textarea class="form-control" @bind="@body" rows="5" readonly />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
}
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label class="col-sm-3" For="message" HelpText="The contents of the message" ResourceKey="Message">Message:</Label>
|
||||||
</div>
|
<div class="col-sm-9">
|
||||||
|
<textarea id="message" class="form-control" @bind="@body" rows="5" readonly />
|
||||||
|
</div>
|
||||||
@if (reply != string.Empty)
|
</div>
|
||||||
{
|
</div>
|
||||||
<button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (title == "From")
|
<div class="container">
|
||||||
{
|
<div class="row mb-1 align-items-center">
|
||||||
<button type="button" class="btn btn-primary" @onclick="Reply">@Localizer["Reply"]</button>
|
<Label Class="col-sm-3" For="username" HelpText="The user who will be the recipient of the message" ResourceKey="To">To:</Label>
|
||||||
}
|
<div class="col-sm-9">
|
||||||
}
|
<input id="username" class="form-control" @bind="@username" readonly />
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
</div>
|
||||||
<br />
|
</div>
|
||||||
<br />
|
<div class="row mb-1 align-items-center">
|
||||||
@if (title == "To")
|
<Label Class="col-sm-3" For="subject" HelpText="The subject of the message" ResourceKey="Subject">Subject:</Label>
|
||||||
{
|
<div class="col-sm-9">
|
||||||
<div class="control-group">
|
<input id="subject" class="form-control" @bind="@subject" readonly="@(!reply)" />
|
||||||
<label class="control-label">@Localizer["OriginalMessage"] </label>
|
</div>
|
||||||
<textarea class="form-control" @bind="@reply" rows="5" readonly />
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label class="col-sm-3" For="message" HelpText="The content of the message" ResourceKey="Message">Message:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="message" class="form-control" @bind="@body" rows="5" readonly="@(!reply)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (reply)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary me-2" @onclick="Send">@SharedLocalizer["Send"]</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (title == "From" && username != Localizer["System"])
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary me-2" @onclick="Reply">@Localizer["Reply"]</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -105,7 +82,7 @@
|
|||||||
private string subject = string.Empty;
|
private string subject = string.Empty;
|
||||||
private string createdon = string.Empty;
|
private string createdon = string.Empty;
|
||||||
private string body = string.Empty;
|
private string body = string.Empty;
|
||||||
private string reply = string.Empty;
|
private bool reply = false;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
public override string Title => "View Notification";
|
public override string Title => "View Notification";
|
||||||
@@ -145,11 +122,17 @@
|
|||||||
}
|
}
|
||||||
if (username == "")
|
if (username == "")
|
||||||
{
|
{
|
||||||
username = "System";
|
username = Localizer["System"];
|
||||||
}
|
}
|
||||||
subject = notification.Subject;
|
subject = notification.Subject;
|
||||||
createdon = notification.CreatedOn.ToString();
|
createdon = notification.CreatedOn.ToString();
|
||||||
body = notification.Body;
|
body = notification.Body;
|
||||||
|
|
||||||
|
if (title == "From" && !notification.IsRead)
|
||||||
|
{
|
||||||
|
notification.IsRead = true;
|
||||||
|
notification = await NotificationService.UpdateNotificationAsync(notification);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -162,12 +145,16 @@
|
|||||||
private void Reply()
|
private void Reply()
|
||||||
{
|
{
|
||||||
title = "To";
|
title = "To";
|
||||||
if (!subject.Contains("RE:"))
|
if (!subject.Contains(Localizer["RE:"]))
|
||||||
{
|
{
|
||||||
subject = "RE: " + subject;
|
subject = Localizer["RE"] + " " + subject;
|
||||||
}
|
}
|
||||||
reply = body;
|
body = $"\n\n____________________________________________\n" +
|
||||||
body = "\n\n____________________________________________\nSent: " + createdon + "\nSubject: " + subject + "\n\n" + body;
|
$"{Localizer["From.Text"]} {username}\n" +
|
||||||
|
$"{Localizer["Date.Text"]} {createdon}\n" +
|
||||||
|
$"{Localizer["Subject.Text"]} {subject}\n\n" +
|
||||||
|
body;
|
||||||
|
reply = true;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +168,7 @@
|
|||||||
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, notificationid);
|
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, notificationid);
|
||||||
notification = await NotificationService.AddNotificationAsync(notification);
|
notification = await NotificationService.AddNotificationAsync(notification);
|
||||||
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
|
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,118 +1,143 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Users
|
@namespace Oqtane.Modules.Admin.Users
|
||||||
|
@using System.Text.RegularExpressions;
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
@inject IProfileService ProfileService
|
@inject IProfileService ProfileService
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
|
@inject ITimeZoneService TimeZoneService
|
||||||
@inject IStringLocalizer<Add> Localizer
|
@inject IStringLocalizer<Add> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<TabStrip>
|
@if (_initialized)
|
||||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
{
|
||||||
@if (profiles != null)
|
<TabStrip>
|
||||||
{
|
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||||
|
@if (_profiles != null)
|
||||||
|
{
|
||||||
|
<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>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="username" class="form-control" @bind="@_username" />
|
||||||
|
</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">
|
||||||
|
<input id="email" class="form-control" @bind="@_email" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="displayname" class="form-control" @bind="@_displayname" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var timezone in _timezones)
|
||||||
|
{
|
||||||
|
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="notify" class="form-select" @bind="@_notify" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<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>
|
@foreach (Profile profile in _profiles)
|
||||||
<div class="col-sm-9">
|
|
||||||
<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">@_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">@_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">
|
|
||||||
<input id="email" class="form-control" @bind="@email" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
}
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
|
||||||
@if (profiles != null)
|
|
||||||
{
|
|
||||||
<div class="container">
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
@foreach (Profile profile in profiles)
|
|
||||||
{
|
{
|
||||||
var p = profile;
|
var p = profile;
|
||||||
if (p.Category != category)
|
if (p.Category != _category)
|
||||||
{
|
{
|
||||||
<div class="col text-center pb-2">
|
<div class="col text-center pb-2">
|
||||||
<strong>@p.Category</strong>
|
<strong>@p.Category</strong>
|
||||||
</div>
|
</div>
|
||||||
category = p.Category;
|
_category = p.Category;
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@if (p.IsRequired)
|
@if (!string.IsNullOrEmpty(p.Options))
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||||
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||||
|
{
|
||||||
|
<option value="@option" selected>@option</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<option value="@option">@option</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
}
|
}
|
||||||
else
|
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.Rows == 1)
|
||||||
|
{
|
||||||
|
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
</TabPanel>
|
||||||
</TabPanel>
|
</TabStrip>
|
||||||
</TabStrip>
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
}
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string username = string.Empty;
|
private List<Models.TimeZone> _timezones;
|
||||||
private string _password = string.Empty;
|
private bool _initialized = false;
|
||||||
private string _passwordtype = "password";
|
private string _username = string.Empty;
|
||||||
private string _togglepassword = string.Empty;
|
private string _email = string.Empty;
|
||||||
private string confirm = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private string email = string.Empty;
|
private string _timezoneid = string.Empty;
|
||||||
private string displayname = string.Empty;
|
private string _notify = "True";
|
||||||
private List<Profile> profiles;
|
private List<Profile> _profiles;
|
||||||
private Dictionary<string, string> settings;
|
private Dictionary<string, string> _settings;
|
||||||
private string category = string.Empty;
|
private string _category = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||||
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||||
settings = new Dictionary<string, string>();
|
_settings = new Dictionary<string, string>();
|
||||||
|
_timezoneid = PageState.Site.TimeZoneId;
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -122,50 +147,47 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||||
=> SettingService.GetSetting(settings, SettingName, DefaultValue);
|
{
|
||||||
|
string value = SettingService.GetSetting(_settings, SettingName, DefaultValue);
|
||||||
|
if (value.Contains("]"))
|
||||||
|
{
|
||||||
|
value = value.Substring(value.IndexOf("]") + 1);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SaveUser()
|
private async Task SaveUser()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (username != string.Empty && _password != string.Empty && confirm != string.Empty && email != string.Empty && ValidateProfiles())
|
if (_username != string.Empty && _email != string.Empty)
|
||||||
{
|
{
|
||||||
if (_password == confirm)
|
if (ValidateProfiles())
|
||||||
{
|
{
|
||||||
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
|
var user = new User();
|
||||||
if (user == null)
|
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.TimeZoneId = _timezoneid;
|
||||||
|
user.PhotoFileId = null;
|
||||||
|
user.SuppressNotification = !bool.Parse(_notify);
|
||||||
|
|
||||||
|
user = await UserService.AddUserAsync(user);
|
||||||
|
|
||||||
|
if (user != null)
|
||||||
{
|
{
|
||||||
user = new User();
|
await SettingService.UpdateUserSettingsAsync(_settings, user.UserId);
|
||||||
user.SiteId = PageState.Site.SiteId;
|
await logger.LogInformation("User Created {User}", user);
|
||||||
user.Username = username;
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
user.Password = _password;
|
|
||||||
user.Email = email;
|
|
||||||
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
|
|
||||||
user.PhotoFileId = null;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Username.Exists"], MessageType.Warning);
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -174,45 +196,45 @@
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", username, email, ex.Message);
|
await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", _username, _email, ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ValidateProfiles()
|
private bool ValidateProfiles()
|
||||||
{
|
{
|
||||||
bool valid = true;
|
foreach (Profile profile in _profiles)
|
||||||
foreach (Profile profile in profiles)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue))
|
var value = GetProfileValue(profile.Name, string.Empty);
|
||||||
|
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||||
{
|
{
|
||||||
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
|
_settings = SettingService.SetSetting(_settings, profile.Name, profile.DefaultValue);
|
||||||
}
|
}
|
||||||
if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
|
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
{
|
{
|
||||||
valid = false;
|
if (profile.IsRequired && string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(profile.Validation))
|
||||||
|
{
|
||||||
|
Regex regex = new Regex(profile.Validation);
|
||||||
|
bool valid = regex.Match(value).Success;
|
||||||
|
if (!valid)
|
||||||
|
{
|
||||||
|
AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return valid;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||||
{
|
{
|
||||||
var value = (string)e.Value;
|
var value = (string)e.Value;
|
||||||
settings = SettingService.SetSetting(settings, SettingName, value);
|
_settings = SettingService.SetSetting(_settings, SettingName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TogglePassword()
|
|
||||||
{
|
|
||||||
if (_passwordtype == "password")
|
|
||||||
{
|
|
||||||
_passwordtype = "text";
|
|
||||||
_togglepassword = SharedLocalizer["HidePassword"];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_passwordtype = "password";
|
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,106 +1,117 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Users
|
@namespace Oqtane.Modules.Admin.Users
|
||||||
|
@using System.Text.RegularExpressions;
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
@inject IProfileService ProfileService
|
@inject IProfileService ProfileService
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
|
@inject ITimeZoneService TimeZoneService
|
||||||
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (PageState.User != null && photo != null)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
<img src="@photo.Url" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
|
<TabStrip>
|
||||||
}
|
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||||
else
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
{
|
|
||||||
<br />
|
|
||||||
}
|
|
||||||
<TabStrip>
|
|
||||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
|
||||||
@if (profiles != null)
|
|
||||||
{
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username"></Label>
|
<Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username">Username:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="username" class="form-control" @bind="@username" readonly />
|
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
|
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password">Password:</Label>
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_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="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@confirm" autocomplete="new-password" />
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
|
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm">Confirm Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="email" class="form-control" @bind="@email" />
|
<div class="input-group">
|
||||||
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email">Email:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
<input id="email" class="form-control" @bind="@_email" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="@photofileid.ToString()" HelpText="A photo of the user" ResourceKey="Photo"></Label>
|
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Confirmed?</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<FileManager FileId="@photofileid" @ref="filemanager" />
|
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<select id="isdeleted" class="form-select" @bind="@isdeleted">
|
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin"></Label>
|
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName">Full Name:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="lastlogin" class="form-control" @bind="@lastlogin" readonly />
|
<input id="displayname" class="form-control" @bind="@_displayname" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="lastipaddress" HelpText="The IP Address of the user recorded during their last login" ResourceKey="LastIPAddress"></Label>
|
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="lastipaddress" class="form-control" @bind="@lastipaddress" readonly />
|
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||||
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var timezone in _timezones)
|
||||||
|
{
|
||||||
|
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted">Deleted?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="isdeleted" class="form-select" @bind="@_isdeleted">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin">Last Login:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="lastlogin" class="form-control" @bind="@_lastlogin" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="lastipaddress" HelpText="The IP Address of the user recorded during their last login" ResourceKey="LastIPAddress">Last IP Address:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="lastipaddress" class="form-control" @bind="@_lastipaddress" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
</TabPanel>
|
||||||
</TabPanel>
|
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
|
||||||
@if (profiles != null)
|
|
||||||
{
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@foreach (Profile profile in profiles)
|
@foreach (Profile profile in _profiles)
|
||||||
{
|
{
|
||||||
var p = profile;
|
var p = profile;
|
||||||
if (p.Category != category)
|
if (p.Category != _category)
|
||||||
{
|
{
|
||||||
<div class="col text-center pb-2">
|
<div class="col text-center pb-2">
|
||||||
<strong>@p.Category</strong>
|
<strong>@p.Category</strong>
|
||||||
</div>
|
</div>
|
||||||
category = p.Category;
|
_category = p.Category;
|
||||||
}
|
}
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||||
@@ -123,13 +134,13 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@if (p.IsRequired)
|
@if (p.Rows == 1)
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
|
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -137,122 +148,144 @@ else
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
</TabPanel>
|
||||||
</TabPanel>
|
</TabStrip>
|
||||||
</TabStrip>
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost)
|
||||||
<br />
|
{
|
||||||
<br />
|
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
|
||||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
|
}
|
||||||
|
@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 int userid;
|
private List<Models.TimeZone> _timezones;
|
||||||
private string username = string.Empty;
|
private bool _initialized = false;
|
||||||
private string _password = string.Empty;
|
private string _passwordrequirements;
|
||||||
private string _passwordtype = "password";
|
private int _userid;
|
||||||
private string _togglepassword = string.Empty;
|
private string _username = string.Empty;
|
||||||
private string confirm = string.Empty;
|
private string _password = string.Empty;
|
||||||
private string email = string.Empty;
|
private string _passwordtype = "password";
|
||||||
private string displayname = string.Empty;
|
private string _togglepassword = string.Empty;
|
||||||
private FileManager filemanager;
|
private string _confirm = string.Empty;
|
||||||
private int photofileid = -1;
|
private string _email = string.Empty;
|
||||||
private File photo = null;
|
private string _confirmed = string.Empty;
|
||||||
private string isdeleted;
|
private string _displayname = string.Empty;
|
||||||
private string lastlogin;
|
private string _timezoneid = string.Empty;
|
||||||
private string lastipaddress;
|
private string _isdeleted;
|
||||||
|
private string _lastlogin;
|
||||||
|
private string _lastipaddress;
|
||||||
|
private bool _ishost = false;
|
||||||
|
|
||||||
private List<Profile> profiles;
|
private List<Profile> _profiles;
|
||||||
private Dictionary<string, string> settings;
|
private Dictionary<string, string> _settings;
|
||||||
private string category = string.Empty;
|
private string _category = string.Empty;
|
||||||
|
|
||||||
private string createdby;
|
private string _createdby;
|
||||||
private DateTime createdon;
|
private DateTime _createdon;
|
||||||
private string modifiedby;
|
private string _modifiedby;
|
||||||
private DateTime modifiedon;
|
private DateTime _modifiedon;
|
||||||
private string deletedby;
|
private string _deletedby;
|
||||||
private DateTime? deletedon;
|
private DateTime? _deletedon;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (PageState.QueryString.ContainsKey("id"))
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
{
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
||||||
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||||
userid = Int32.Parse(PageState.QueryString["id"]);
|
|
||||||
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
username = user.Username;
|
|
||||||
email = user.Email;
|
|
||||||
displayname = user.DisplayName;
|
|
||||||
if (user.PhotoFileId != null)
|
|
||||||
{
|
|
||||||
photofileid = user.PhotoFileId.Value;
|
|
||||||
photo = await FileService.GetFileAsync(photofileid);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
photofileid = -1;
|
|
||||||
photo = null;
|
|
||||||
}
|
|
||||||
isdeleted = user.IsDeleted.ToString();
|
|
||||||
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
|
|
||||||
lastipaddress = user.LastIPAddress;
|
|
||||||
|
|
||||||
settings = await SettingService.GetUserSettingsAsync(user.UserId);
|
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
|
||||||
createdby = user.CreatedBy;
|
{
|
||||||
createdon = user.CreatedOn;
|
_userid = UserId;
|
||||||
modifiedby = user.ModifiedBy;
|
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
|
||||||
modifiedon = user.ModifiedOn;
|
if (user != null)
|
||||||
deletedby = user.DeletedBy;
|
{
|
||||||
deletedon = user.DeletedOn;
|
_username = user.Username;
|
||||||
|
_email = user.Email;
|
||||||
|
_confirmed = user.EmailConfirmed.ToString();
|
||||||
|
_displayname = user.DisplayName;
|
||||||
|
_timezoneid = PageState.User.TimeZoneId;
|
||||||
|
_isdeleted = user.IsDeleted.ToString();
|
||||||
|
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
|
||||||
|
_lastipaddress = user.LastIPAddress;
|
||||||
|
_ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
||||||
|
|
||||||
|
_settings = user.Settings;
|
||||||
|
_createdby = user.CreatedBy;
|
||||||
|
_createdon = user.CreatedOn;
|
||||||
|
_modifiedby = user.ModifiedBy;
|
||||||
|
_modifiedon = user.ModifiedOn;
|
||||||
|
_deletedby = user.DeletedBy;
|
||||||
|
_deletedon = user.DeletedOn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message);
|
await logger.LogError(ex, "Error Loading User {UserId} {Error}", _userid, ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetProfileValue(string SettingName, string DefaultValue)
|
private string GetProfileValue(string SettingName, string DefaultValue)
|
||||||
=> SettingService.GetSetting(settings, SettingName, DefaultValue);
|
{
|
||||||
|
string value = SettingService.GetSetting(_settings, SettingName, DefaultValue);
|
||||||
|
if (value.Contains("]"))
|
||||||
|
{
|
||||||
|
value = value.Substring(value.IndexOf("]") + 1);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SaveUser()
|
private async Task SaveUser()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (username != string.Empty && email != string.Empty && ValidateProfiles())
|
if (_username != string.Empty && _email != string.Empty)
|
||||||
{
|
{
|
||||||
if (_password == confirm)
|
if (_password == _confirm)
|
||||||
{
|
{
|
||||||
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
if (ValidateProfiles())
|
||||||
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.PhotoFileId = filemanager.GetFileId();
|
|
||||||
if (user.PhotoFileId == -1)
|
|
||||||
{
|
{
|
||||||
user.PhotoFileId = null;
|
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
|
||||||
|
user.SiteId = PageState.Site.SiteId;
|
||||||
|
user.Username = _username;
|
||||||
|
user.Password = _password;
|
||||||
|
user.Email = _email;
|
||||||
|
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||||
|
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||||
|
user.TimeZoneId = _timezoneid;
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
user.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
||||||
|
}
|
||||||
|
|
||||||
|
user = await UserService.UpdateUserAsync(user);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
await SettingService.UpdateUserSettingsAsync(_settings, user.UserId);
|
||||||
|
await logger.LogInformation("User Saved {User}", user);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
|
|
||||||
|
|
||||||
user = await UserService.UpdateUserAsync(user);
|
|
||||||
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
|
|
||||||
await logger.LogInformation("User Saved {User}", user);
|
|
||||||
|
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -266,32 +299,84 @@ else
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Saving User {Username} {Email} {Error}", username, email, ex.Message);
|
await logger.LogError(ex, "Error Saving User {Username} {Email} {Error}", _username, _email, ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.User.Save"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.User.Save"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ImpersonateUser()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await logger.LogInformation(LogFunction.Security, "User {Username} Impersonated By Administrator {Administrator}", _username, PageState.User.Username);
|
||||||
|
|
||||||
|
// post back to the server so that the cookies are set correctly
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, returnurl = PageState.Alias.Path };
|
||||||
|
string url = Utilities.TenantUrl(PageState.Alias, "/pages/impersonate/");
|
||||||
|
await interop.SubmitForm(url, fields);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Impersonating User {Username} {Error}", _username, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.User.Impersonate"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteUser()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _userid != PageState.User.UserId)
|
||||||
|
{
|
||||||
|
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
|
||||||
|
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
|
||||||
|
await logger.LogInformation("User Permanently Deleted {User}", user);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Permanently Deleting User {UserId} {Error}", _userid, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.DeleteUser"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool ValidateProfiles()
|
private bool ValidateProfiles()
|
||||||
{
|
{
|
||||||
bool valid = true;
|
foreach (Profile profile in _profiles)
|
||||||
foreach (Profile profile in profiles)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)) && !string.IsNullOrEmpty(profile.DefaultValue))
|
var value = GetProfileValue(profile.Name, string.Empty);
|
||||||
|
if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(profile.DefaultValue))
|
||||||
{
|
{
|
||||||
settings = SettingService.SetSetting(settings, profile.Name, profile.DefaultValue);
|
_settings = SettingService.SetSetting(_settings, profile.Name, profile.DefaultValue);
|
||||||
}
|
}
|
||||||
if (profile.IsRequired && string.IsNullOrEmpty(SettingService.GetSetting(settings, profile.Name, string.Empty)))
|
if (!profile.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
{
|
{
|
||||||
valid = false;
|
if (profile.IsRequired && string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
AddModuleMessage(string.Format(SharedLocalizer["ProfileRequired"], profile.Title), MessageType.Warning);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(profile.Validation))
|
||||||
|
{
|
||||||
|
Regex regex = new Regex(profile.Validation);
|
||||||
|
bool valid = regex.Match(value).Success;
|
||||||
|
if (!valid)
|
||||||
|
{
|
||||||
|
AddModuleMessage(string.Format(SharedLocalizer["ProfileInvalid"], profile.Title), MessageType.Warning);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return valid;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
private void ProfileChanged(ChangeEventArgs e, string SettingName)
|
||||||
{
|
{
|
||||||
var value = (string)e.Value;
|
var value = (string)e.Value;
|
||||||
settings = SettingService.SetSetting(settings, SettingName, value);
|
_settings = SettingService.SetSetting(_settings, SettingName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TogglePassword()
|
private void TogglePassword()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
21
Oqtane.Client/Modules/Admin/Users/ModuleInfo.cs
Normal file
21
Oqtane.Client/Modules/Admin/Users/ModuleInfo.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Oqtane.Documentation;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Modules.Admin.Users
|
||||||
|
{
|
||||||
|
[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 = "Users",
|
||||||
|
Description = "Manage Users",
|
||||||
|
Categories = "Admin",
|
||||||
|
Version = Constants.Version,
|
||||||
|
PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit}," +
|
||||||
|
$"{EntityNames.User}:{PermissionNames.Write}:{RoleNames.Admin}," +
|
||||||
|
$"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,13 +34,13 @@ else
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="effectiveDate" class="form-control" @bind="@effectivedate" />
|
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="expiryDate" class="form-control" @bind="@expirydate" />
|
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -53,17 +53,17 @@ else
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<Pager Items="@userroles">
|
<Pager Items="@userroles">
|
||||||
<Header>
|
<Header>
|
||||||
<th>@Localizer["Roles"]</th>
|
<th>@Localizer["Roles"]</th>
|
||||||
<th>@Localizer["Effective"]</th>
|
<th>@Localizer["Effective"]</th>
|
||||||
<th>@Localizer["Expiry"]</th>
|
<th>@Localizer["Expiry"]</th>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td>@context.Role.Name</td>
|
<td>@context.Role.Name</td>
|
||||||
<td>@context.EffectiveDate</td>
|
<td>@Utilities.UtcAsLocalDate(context.EffectiveDate)</td>
|
||||||
<td>@context.ExpiryDate</td>
|
<td>@Utilities.UtcAsLocalDate(context.ExpiryDate)</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" />
|
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.Name == RoleNames.Host && userid == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
|
||||||
</td>
|
</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@@ -75,11 +75,11 @@ else
|
|||||||
private string name = string.Empty;
|
private string name = string.Empty;
|
||||||
private List<Role> roles;
|
private List<Role> roles;
|
||||||
private int roleid = -1;
|
private int roleid = -1;
|
||||||
private string effectivedate = string.Empty;
|
private DateTime? _effectivedate = null;
|
||||||
private string expirydate = string.Empty;
|
private DateTime? _expirydate = null;
|
||||||
private List<UserRole> userroles;
|
private List<UserRole> userroles;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
@@ -88,15 +88,17 @@ else
|
|||||||
userid = Int32.Parse(PageState.QueryString["id"]);
|
userid = Int32.Parse(PageState.QueryString["id"]);
|
||||||
User user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
User user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
||||||
name = user.DisplayName;
|
name = user.DisplayName;
|
||||||
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId, true);
|
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId, true);
|
||||||
roles = roles.Where(item => item.Name != RoleNames.Everyone).ToList();
|
roles.RemoveAll(item => item.Name == RoleNames.Everyone || item.Name == RoleNames.Unauthenticated);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId);
|
roles = await RoleService.GetRolesAsync(PageState.Site.SiteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await GetUserRoles();
|
await GetUserRoles();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -111,6 +113,7 @@ else
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, userid);
|
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, userid);
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -125,26 +128,16 @@ else
|
|||||||
{
|
{
|
||||||
if (roleid != -1)
|
if (roleid != -1)
|
||||||
{
|
{
|
||||||
|
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
|
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
|
||||||
if (userrole != null)
|
if (userrole != null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(effectivedate))
|
userrole.EffectiveDate = _effectivedate;
|
||||||
{
|
userrole.ExpiryDate = _expirydate;
|
||||||
userrole.EffectiveDate = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
userrole.EffectiveDate = DateTime.Parse(effectivedate);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(expirydate))
|
|
||||||
{
|
|
||||||
userrole.ExpiryDate = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
userrole.ExpiryDate = DateTime.Parse(expirydate);
|
|
||||||
}
|
|
||||||
await UserRoleService.UpdateUserRoleAsync(userrole);
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -152,25 +145,8 @@ else
|
|||||||
userrole = new UserRole();
|
userrole = new UserRole();
|
||||||
userrole.UserId = userid;
|
userrole.UserId = userid;
|
||||||
userrole.RoleId = roleid;
|
userrole.RoleId = roleid;
|
||||||
|
userrole.EffectiveDate = Utilities.UtcAsLocalDate(_effectivedate);
|
||||||
if (string.IsNullOrEmpty(effectivedate))
|
userrole.ExpiryDate = Utilities.UtcAsLocalDate(_expirydate);
|
||||||
{
|
|
||||||
userrole.EffectiveDate = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
userrole.EffectiveDate = DateTime.Parse(effectivedate);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(expirydate))
|
|
||||||
{
|
|
||||||
userrole.ExpiryDate = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
userrole.ExpiryDate = DateTime.Parse(expirydate);
|
|
||||||
}
|
|
||||||
|
|
||||||
await UserRoleService.AddUserRoleAsync(userrole);
|
await UserRoleService.AddUserRoleAsync(userrole);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,8 +171,18 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
var userrole = await UserRoleService.GetUserRoleAsync(UserRoleId);
|
||||||
await logger.LogInformation("User Removed From Role {UserRoleId}", 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);
|
AddModuleMessage(Localizer["Success.User.Remove"], MessageType.Success);
|
||||||
await GetUserRoles();
|
await GetUserRoles();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|||||||
69
Oqtane.Client/Modules/Admin/Users/Users.razor
Normal file
69
Oqtane.Client/Modules/Admin/Users/Users.razor
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Users
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IUserService UserService
|
||||||
|
@inject IStringLocalizer<Users> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="importfile" HelpText="Upload or select a tab delimited text file containing user information. The file must be in the Template format specified (Roles can be specified as a comma delimited list)." ResourceKey="ImportFile">Import File:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<FileManager Id="importfile" @ref="_filemanager" Filter="txt" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="notify" class="form-select" @bind="@_notify" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="ImportUsers">@Localizer["Import"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
<a class="btn btn-info" href="/users.txt" target="_new">@Localizer["Template"]</a>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private FileManager _filemanager;
|
||||||
|
|
||||||
|
public override string Title => "Import Users";
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
|
private string _notify = "True";
|
||||||
|
|
||||||
|
private async Task ImportUsers()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fileid = _filemanager.GetFileId();
|
||||||
|
if (fileid != -1)
|
||||||
|
{
|
||||||
|
ShowProgressIndicator();
|
||||||
|
var results = await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid, bool.Parse(_notify));
|
||||||
|
if (bool.Parse(results["Success"]))
|
||||||
|
{
|
||||||
|
AddModuleMessage(string.Format(Localizer["Message.Import.Success"], results["Users"]), MessageType.Success);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error);
|
||||||
|
}
|
||||||
|
HideProgressIndicator();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Import.Validation"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Importing Users {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Import"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,8 +101,8 @@
|
|||||||
_url = visitor.Url;
|
_url = visitor.Url;
|
||||||
_referrer = visitor.Referrer;
|
_referrer = visitor.Referrer;
|
||||||
_visits = visitor.Visits.ToString();
|
_visits = visitor.Visits.ToString();
|
||||||
_visited = visitor.VisitedOn.ToString(CultureInfo.CurrentCulture);
|
_visited = UtcToLocal(visitor.VisitedOn).Value.ToString(CultureInfo.CurrentCulture);
|
||||||
_created = visitor.CreatedOn.ToString(CultureInfo.CurrentCulture);
|
_created = UtcToLocal(visitor.CreatedOn).Value.ToString(CultureInfo.CurrentCulture);
|
||||||
|
|
||||||
if (visitor.UserId != null)
|
if (visitor.UserId != null)
|
||||||
{
|
{
|
||||||
@@ -128,13 +128,6 @@
|
|||||||
|
|
||||||
private string CloseUrl()
|
private string CloseUrl()
|
||||||
{
|
{
|
||||||
if (!PageState.QueryString.ContainsKey("type"))
|
return (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl();
|
||||||
{
|
|
||||||
return NavigateUrl();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return NavigateUrl(PageState.Page.Path, "type=" + PageState.QueryString["type"] + "&days=" + PageState.QueryString["days"] + "&page=" + PageState.QueryString["page"]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ else
|
|||||||
<th>@Localizer["Created"]</th>
|
<th>@Localizer["Created"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="Detail" Parameters="@($"id=" + context.VisitorId.ToString() + "&type=" + _type.ToString() + "&days=" + _days.ToString() + "&page=" + _page.ToString())" ResourceKey="Details" /></td>
|
<td><ActionLink Action="Detail" Text="Detail" Parameters="@($"id={context.VisitorId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, $"type={_type}&days={_days}&page={_page}"))" ResourceKey="Details" /></td>
|
||||||
<td>@context.IPAddress</td>
|
<td>@context.IPAddress</td>
|
||||||
<td>
|
<td>
|
||||||
@if (context.UserId != null)
|
@if (context.UserId != null)
|
||||||
@@ -53,8 +53,8 @@ else
|
|||||||
</td>
|
</td>
|
||||||
<td>@context.Language</td>
|
<td>@context.Language</td>
|
||||||
<td>@context.Visits</td>
|
<td>@context.Visits</td>
|
||||||
<td>@context.VisitedOn</td>
|
<td>@UtcToLocal(context.VisitedOn)</td>
|
||||||
<td>@context.CreatedOn</td>
|
<td>@UtcToLocal(context.CreatedOn)</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
@@ -69,16 +69,22 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="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>
|
<Label Class="col-sm-3" For="filter" HelpText="Comma delimited list of terms which may exist in IP addresses, user agents, or languages identifying visitors which should not be tracked" ResourceKey="Filter">Filter: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="filter" class="form-control" @bind="@_filter" rows="3"></textarea>
|
<textarea id="filter" class="form-control" @bind="@_filter" rows="3"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="retention" HelpText="Number of days of visitor activity to retain" ResourceKey="Retention">Retention (Days): </Label>
|
<Label Class="col-sm-3" For="retention" HelpText="Number of days of visitor activity to retain" ResourceKey="Retention">Retention: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="retention" class="form-control" @bind="@_retention" />
|
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@@ -94,6 +100,18 @@ else
|
|||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
<TabPanel Name="Robots" Heading="Robots.txt" ResourceKey="Robots">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="robots" HelpText="Specify your robots.txt instructions to provide bots with guidance on which parts of your site should be indexed" ResourceKey="Robots">Instructions: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="robots" class="form-control" @bind="@_robots" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,9 +121,11 @@ else
|
|||||||
private int _page = 1;
|
private int _page = 1;
|
||||||
private List<Visitor> _visitors;
|
private List<Visitor> _visitors;
|
||||||
private string _tracking;
|
private string _tracking;
|
||||||
private string _filter = "";
|
private int _duration = 5;
|
||||||
private string _retention = "";
|
private string _filter = "";
|
||||||
|
private int _retention = 30;
|
||||||
private string _correlation = "true";
|
private string _correlation = "true";
|
||||||
|
private string _robots = "";
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
@@ -128,10 +148,12 @@ else
|
|||||||
|
|
||||||
_tracking = PageState.Site.VisitorTracking.ToString();
|
_tracking = PageState.Site.VisitorTracking.ToString();
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
|
_duration = int.Parse(SettingService.GetSetting(settings, "VisitorDuration", "5"));
|
||||||
_retention = SettingService.GetSetting(settings, "VisitorRetention", "30");
|
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
|
||||||
|
_retention = int.Parse(SettingService.GetSetting(settings, "VisitorRetention", "30"));
|
||||||
_correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true");
|
_correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true");
|
||||||
}
|
_robots = SettingService.GetSetting(settings, "Robots", "");
|
||||||
|
}
|
||||||
|
|
||||||
private async void TypeChanged(ChangeEventArgs e)
|
private async void TypeChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -179,9 +201,11 @@ else
|
|||||||
await SiteService.UpdateSiteAsync(site);
|
await SiteService.UpdateSiteAsync(site);
|
||||||
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true);
|
settings = SettingService.SetSetting(settings, "VisitorDuration", _duration.ToString(), true);
|
||||||
settings = SettingService.SetSetting(settings, "VisitorRetention", _retention, true);
|
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "VisitorRetention", _retention.ToString(), true);
|
||||||
settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true);
|
settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "Robots", _robots, true);
|
||||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
|
||||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
|
|||||||
@@ -1,49 +1,108 @@
|
|||||||
@namespace Oqtane.Modules.Controls
|
@namespace Oqtane.Modules.Controls
|
||||||
|
@using System.Text.Json
|
||||||
@inherits LocalizableComponent
|
@inherits LocalizableComponent
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
@if (_visible)
|
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
|
||||||
{
|
{
|
||||||
<div class="app-actiondialog">
|
@if (_visible)
|
||||||
<div class="modal" tabindex="-1" role="dialog">
|
{
|
||||||
<div class="modal-dialog">
|
<div class="app-actiondialog">
|
||||||
<div class="modal-content">
|
<div class="modal" tabindex="-1" role="dialog">
|
||||||
<div class="modal-header">
|
<div class="modal-dialog">
|
||||||
<h5 class="modal-title">@Header</h5>
|
<div class="modal-content">
|
||||||
<button type="button" class="btn-close" aria-label="Close" @onclick="DisplayModal"></button>
|
<div class="modal-header">
|
||||||
</div>
|
<h5 class="modal-title">@Header</h5>
|
||||||
<div class="modal-body">
|
<button type="button" class="btn-close" aria-label="Close" @onclick="DisplayModal"></button>
|
||||||
<p>@Message</p>
|
</div>
|
||||||
</div>
|
<div class="modal-body">
|
||||||
<div class="modal-footer">
|
<p>@Message</p>
|
||||||
@if (!string.IsNullOrEmpty(Action))
|
</div>
|
||||||
{
|
<div class="modal-footer">
|
||||||
<button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
|
@if (!string.IsNullOrEmpty(Action))
|
||||||
}
|
{
|
||||||
<button type="button" class="btn btn-secondary" @onclick="DisplayModal">@Localize("Cancel")</button>
|
<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>
|
</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 {
|
@code {
|
||||||
private bool _visible = false;
|
private bool _visible = false;
|
||||||
private string _permissions = string.Empty;
|
private List<Permission> _permissions;
|
||||||
private bool _editmode = false;
|
private bool _editmode = false;
|
||||||
private bool _authorized = false;
|
private bool _authorized = false;
|
||||||
private string _iconSpan = string.Empty;
|
private string _iconSpan = string.Empty;
|
||||||
|
private string _openIconSpan = string.Empty;
|
||||||
|
private string _openText = string.Empty;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Header { get; set; } // required
|
public string Header { get; set; } // required
|
||||||
@@ -60,12 +119,21 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
|
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Permissions { get; set; } // optional - can be used to specify a permission string
|
public string Permissions { get; set; } // deprecated - use PermissionList instead
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Class { get; set; } // optional
|
public string Class { get; set; } // optional
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string ConfirmClass { get; set; } // optional - for Confirm modal button
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string CancelClass { get; set; } // optional - for Cancel modal button
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool Disabled { get; set; } // optional
|
public bool Disabled { get; set; } // optional
|
||||||
|
|
||||||
@@ -78,6 +146,20 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
|
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool IconOnly { get; set; } // optional - specifies only icon in opening link
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Id { get; set; } // optional - specifies a unique id for the compoment - required when there are multiple component instances on a page in static rendering
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(Permissions))
|
||||||
|
{
|
||||||
|
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
base.OnParametersSet();
|
base.OnParametersSet();
|
||||||
@@ -86,31 +168,60 @@
|
|||||||
{
|
{
|
||||||
Text = Action;
|
Text = Action;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Class))
|
if (string.IsNullOrEmpty(Class))
|
||||||
{
|
{
|
||||||
Class = "btn btn-success";
|
Class = "btn btn-success";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(ConfirmClass))
|
||||||
|
{
|
||||||
|
ConfirmClass = Class;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(CancelClass))
|
||||||
|
{
|
||||||
|
CancelClass = "btn btn-secondary";
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(EditMode))
|
if (!string.IsNullOrEmpty(EditMode))
|
||||||
{
|
{
|
||||||
_editmode = bool.Parse(EditMode);
|
_editmode = bool.Parse(EditMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(IconName))
|
|
||||||
{
|
|
||||||
if (!IconName.Contains(" "))
|
|
||||||
{
|
|
||||||
IconName = "oi oi-" + IconName;
|
|
||||||
}
|
|
||||||
_iconSpan = $"<span class=\"{IconName}\"></span> ";
|
|
||||||
}
|
|
||||||
|
|
||||||
Text = Localize(nameof(Text), Text);
|
Text = Localize(nameof(Text), Text);
|
||||||
Header = Localize(nameof(Header), Header);
|
Header = Localize(nameof(Header), Header);
|
||||||
Message = Localize(nameof(Message), Message);
|
Message = Localize(nameof(Message), Message);
|
||||||
|
|
||||||
_permissions = (string.IsNullOrEmpty(Permissions)) ? ModuleState.Permissions : Permissions;
|
_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();
|
_authorized = IsAuthorized();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(Id)) Id = "1";
|
||||||
|
|
||||||
|
if (PageState.QueryString.ContainsKey("dialog"))
|
||||||
|
{
|
||||||
|
_visible = (PageState.QueryString["dialog"] == Id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsAuthorized()
|
private bool IsAuthorized()
|
||||||
@@ -162,12 +273,22 @@
|
|||||||
private void DisplayModal()
|
private void DisplayModal()
|
||||||
{
|
{
|
||||||
_visible = !_visible;
|
_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()
|
private void Confirm()
|
||||||
{
|
{
|
||||||
DisplayModal();
|
|
||||||
OnClick();
|
OnClick();
|
||||||
|
DisplayModal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
@namespace Oqtane.Modules.Controls
|
@namespace Oqtane.Modules.Controls
|
||||||
|
@using System.Net
|
||||||
|
@using System.Text.Json
|
||||||
@inherits LocalizableComponent
|
@inherits LocalizableComponent
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
|
|
||||||
@@ -6,7 +8,7 @@
|
|||||||
{
|
{
|
||||||
if (Disabled)
|
if (Disabled)
|
||||||
{
|
{
|
||||||
<button type="button" class="@_classname" style="@_style" disabled>@((MarkupString)_iconSpan) @_text</button>
|
<NavLink class="@($"{_classname} disabled")" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -22,103 +24,145 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _text = string.Empty;
|
private string _text = string.Empty;
|
||||||
private string _parameters = string.Empty;
|
private int _moduleId = -1;
|
||||||
private string _url = string.Empty;
|
private string _path = string.Empty;
|
||||||
private string _permissions = string.Empty;
|
private string _parameters = string.Empty;
|
||||||
private bool _editmode = false;
|
private string _url = string.Empty;
|
||||||
private bool _authorized = false;
|
private List<Permission> _permissions;
|
||||||
private string _classname = "btn btn-primary";
|
private bool _editmode = false;
|
||||||
private string _style = string.Empty;
|
private bool _authorized = false;
|
||||||
private string _iconSpan = string.Empty;
|
private string _classname = "btn btn-primary";
|
||||||
|
private string _style = string.Empty;
|
||||||
|
private string _iconSpan = string.Empty;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Action { get; set; } // required
|
public string Action { get; set; } // required
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Text { get; set; } // optional - defaults to Action if not specified
|
public string Text { get; set; } // optional - defaults to Action if not specified
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Parameters { get; set; } // optional - querystring parameters should be in the form of "id=x&name=y"
|
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid
|
public string Path { get; set; } = null; // optional - allows the link to target a specific page
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Action OnClick { get; set; } = null; // optional - executes a method in the calling component
|
public string Parameters { get; set; } // optional - querystring parameters should be in the form of "id=x&name=y"
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
|
public Action OnClick { get; set; } = null; // optional - executes a method in the calling component
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Permissions { get; set; } // optional - can be used to specify a permission string
|
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool Disabled { get; set; } // optional
|
public string Permissions { get; set; } // deprecated - use PermissionList instead
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string EditMode { get; set; } // optional - specifies if an authorized user must be in edit mode to see the action - default is false.
|
public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Class { get; set; } // optional - defaults to primary if not specified
|
public bool Disabled { get; set; } // optional
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Style { get; set; } // optional
|
public string EditMode { get; set; } // optional - specifies if an authorized user must be in edit mode to see the action - default is false.
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
|
public string Class { get; set; } // optional - defaults to primary if not specified
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool IconOnly { get; set; } // optional - specifies only icon in link
|
public string Style { get; set; } // optional
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
[Parameter]
|
||||||
{
|
public string IconName { get; set; } // optional - specifies an icon for the link - default is no icon
|
||||||
base.OnParametersSet();
|
|
||||||
|
|
||||||
_text = Action;
|
[Parameter]
|
||||||
if (!string.IsNullOrEmpty(Text))
|
public bool IconOnly { get; set; } // optional - specifies only icon in link
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string ReturnUrl { get; set; } // optional - used to set a url to redirect to
|
||||||
|
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(Permissions))
|
||||||
|
{
|
||||||
|
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
base.OnParametersSet();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Text))
|
||||||
|
{
|
||||||
|
_text = Localize(nameof(Text), Text);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_text = Localize(nameof(Action), Action);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IconOnly && !string.IsNullOrEmpty(IconName))
|
||||||
|
{
|
||||||
|
_text = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
_moduleId = ModuleState.ModuleId;
|
||||||
|
if (ModuleId != -1)
|
||||||
|
{
|
||||||
|
_moduleId = ModuleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
_path = PageState.Page.Path;
|
||||||
|
if (Path != null)
|
||||||
|
{
|
||||||
|
_path = Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Parameters))
|
||||||
|
{
|
||||||
|
_parameters = Parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Class))
|
||||||
|
{
|
||||||
|
_classname = Class;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Style))
|
||||||
|
{
|
||||||
|
_style = Style;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(EditMode))
|
||||||
|
{
|
||||||
|
_editmode = bool.Parse(EditMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(IconName))
|
||||||
|
{
|
||||||
|
// Check if IconName starts with "oi oi-"
|
||||||
|
bool startsWithOiOi = IconName.StartsWith("oi oi-");
|
||||||
|
|
||||||
|
if (!startsWithOiOi && !IconName.Contains(" "))
|
||||||
|
{
|
||||||
|
IconName = "oi oi-" + IconName;
|
||||||
|
}
|
||||||
|
_iconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : " ")}";
|
||||||
|
}
|
||||||
|
|
||||||
|
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
|
||||||
|
|
||||||
|
_url = EditUrl(_path, _moduleId, Action, _parameters);
|
||||||
|
if (!string.IsNullOrEmpty(ReturnUrl))
|
||||||
{
|
{
|
||||||
_text = Text;
|
_url += ((_url.Contains("?")) ? "&" : "?") + $"returnurl={WebUtility.UrlEncode(ReturnUrl)}";
|
||||||
}
|
}
|
||||||
|
_authorized = IsAuthorized();
|
||||||
if (IconOnly && !string.IsNullOrEmpty(IconName))
|
|
||||||
{
|
|
||||||
_text = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Parameters))
|
|
||||||
{
|
|
||||||
_parameters = Parameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Class))
|
|
||||||
{
|
|
||||||
_classname = Class;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Style))
|
|
||||||
{
|
|
||||||
_style = Style;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(EditMode))
|
|
||||||
{
|
|
||||||
_editmode = bool.Parse(EditMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(IconName))
|
|
||||||
{
|
|
||||||
if (!IconName.Contains(" "))
|
|
||||||
{
|
|
||||||
IconName = "oi oi-" + IconName;
|
|
||||||
}
|
|
||||||
_iconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : " ")}";
|
|
||||||
}
|
|
||||||
|
|
||||||
_permissions = (string.IsNullOrEmpty(Permissions)) ? ModuleState.Permissions : Permissions;
|
|
||||||
_text = Localize(nameof(Text), _text);
|
|
||||||
_url = (ModuleId == -1) ? EditUrl(Action, _parameters) : EditUrl(ModuleId, Action, _parameters);
|
|
||||||
_authorized = IsAuthorized();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsAuthorized()
|
private bool IsAuthorized()
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
if (CreatedOn != null)
|
if (CreatedOn != null)
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["On"]} <b>{CreatedOn.Value.ToString(DateTimeFormat)}</b>";
|
_text += $" {Localizer["On"]} <b>{UtcToLocal(CreatedOn).Value.ToString(DateTimeFormat)}</ b >";
|
||||||
}
|
}
|
||||||
|
|
||||||
_text += "</p>";
|
_text += "</p>";
|
||||||
@@ -64,12 +64,12 @@
|
|||||||
|
|
||||||
if (!String.IsNullOrEmpty(ModifiedBy))
|
if (!String.IsNullOrEmpty(ModifiedBy))
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["by"]} <b>{ModifiedBy}</b>";
|
_text += $" {Localizer["By"]} <b>{ModifiedBy}</b>";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ModifiedOn != null)
|
if (ModifiedOn != null)
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["on"]} <b>{ModifiedOn.Value.ToString(DateTimeFormat)}</b>";
|
_text += $" {Localizer["On"]} <b>{UtcToLocal(ModifiedOn).Value.ToString(DateTimeFormat)}</ b >";
|
||||||
}
|
}
|
||||||
|
|
||||||
_text += "</p>";
|
_text += "</p>";
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
|
|
||||||
if (DeletedOn != null)
|
if (DeletedOn != null)
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["On"]} <b>{DeletedOn.Value.ToString(DateTimeFormat)}</b>";
|
_text += $" {Localizer["On"]} <b>{UtcToLocal(DeletedOn).Value.ToString(DateTimeFormat)}</ b >";
|
||||||
}
|
}
|
||||||
|
|
||||||
_text += "</p>";
|
_text += "</p>";
|
||||||
|
|||||||
174
Oqtane.Client/Modules/Controls/AutoComplete.razor
Normal file
174
Oqtane.Client/Modules/Controls/AutoComplete.razor
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
@namespace Oqtane.Modules.Controls
|
||||||
|
@inherits LocalizableComponent
|
||||||
|
|
||||||
|
<div class="app-autocomplete">
|
||||||
|
<input class="form-control" value="@Value" @oninput="OnInput" @onkeyup="OnKeyUp" placeholder="@Placeholder" autocomplete="off" @attributes="InputAttributes" />
|
||||||
|
@if (_results != null)
|
||||||
|
{
|
||||||
|
<select class="form-select" style="position: relative;" value="@Value" size="@Rows" @onkeyup="OnKeyUp" @onchange="(e => OnChange(e))">
|
||||||
|
@if (_results.Any())
|
||||||
|
{
|
||||||
|
@foreach (var result in _results)
|
||||||
|
{
|
||||||
|
if (result.Value == Value)
|
||||||
|
{
|
||||||
|
<option selected>@result.Value</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<option>@result.Value</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<option disabled>No Results</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
Dictionary<string, string> _results;
|
||||||
|
Dictionary<string, object> InputAttributes { get; set; } = new();
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public Func<string, Task<Dictionary<string, string>>> OnSearch { get; set; } // required - an async delegate method which accepts a filter string parameter and returns a dictionary
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int Characters { get; set; } = 3; // optional - number of characters before search is initiated
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int Rows { get; set; } = 3; // optional - number of result rows to display
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Placeholder { get; set; } // optional - placeholder input text
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Value { get; set; } // value of item selected
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Key { get; set; } // key of item selected
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool Required { get; set; } // optional - if the item is required
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
if (Required)
|
||||||
|
{
|
||||||
|
if (!InputAttributes.ContainsKey(nameof(Required)))
|
||||||
|
{
|
||||||
|
InputAttributes.Add(nameof(Required), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (InputAttributes.ContainsKey(nameof(Required)))
|
||||||
|
{
|
||||||
|
InputAttributes.Remove(nameof(Required));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private async Task OnInput(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
Value = e.Value?.ToString();
|
||||||
|
if (Value?.Length >= Characters)
|
||||||
|
{
|
||||||
|
_results = await OnSearch?.Invoke(Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_results = null;
|
||||||
|
}
|
||||||
|
SetKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnKeyUp(KeyboardEventArgs e)
|
||||||
|
{
|
||||||
|
var index = -1;
|
||||||
|
switch (e.Key)
|
||||||
|
{
|
||||||
|
case "ArrowDown":
|
||||||
|
if (_results == null)
|
||||||
|
{
|
||||||
|
if (Value?.Length >= Characters)
|
||||||
|
{
|
||||||
|
_results = await OnSearch?.Invoke(Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
index = GetIndex();
|
||||||
|
if (index < _results.Count - 1)
|
||||||
|
{
|
||||||
|
Value = _results.ElementAt(index + 1).Value;
|
||||||
|
Key = _results.ElementAt(index + 1).Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "ArrowUp":
|
||||||
|
index = GetIndex();
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
Value = _results.ElementAt(index - 1).Value;
|
||||||
|
Key = _results.ElementAt(index - 1).Key;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "ArrowRight":
|
||||||
|
case "Tab":
|
||||||
|
_results = null;
|
||||||
|
break;
|
||||||
|
case "Enter": // note within a form the enter key submits the entire form
|
||||||
|
case "NumpadEnter":
|
||||||
|
_results = null;
|
||||||
|
break;
|
||||||
|
case "Escape":
|
||||||
|
Value = "";
|
||||||
|
_results = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnChange(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
Value = (string)e.Value;
|
||||||
|
SetKey();
|
||||||
|
_results = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetIndex()
|
||||||
|
{
|
||||||
|
if (_results != null)
|
||||||
|
{
|
||||||
|
for (int index = 0; index < _results.Count; index++)
|
||||||
|
{
|
||||||
|
if (_results.ElementAt(index).Value == Value)
|
||||||
|
{
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetKey()
|
||||||
|
{
|
||||||
|
var index = GetIndex();
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
Key = _results.ElementAt(index).Key;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Key = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Value = "";
|
||||||
|
Key = "";
|
||||||
|
_results = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
@namespace Oqtane.Modules.Controls
|
@namespace Oqtane.Modules.Controls
|
||||||
|
@using System.Threading
|
||||||
@inherits ModuleControlBase
|
@inherits ModuleControlBase
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
|
@inject IUserService UserService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<FileManager> Localizer
|
@inject IStringLocalizer<FileManager> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (_folders != null)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
<div id="@Id" class="container-fluid px-0">
|
<div id="@Id" class="container-fluid px-0">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -39,10 +42,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (FileId != -1 && _file != null && !UploadMultiple)
|
||||||
|
{
|
||||||
|
<input class="form-control" @bind="@_file.Name" disabled />
|
||||||
|
}
|
||||||
|
}
|
||||||
@if (ShowUpload && _haseditpermission)
|
@if (ShowUpload && _haseditpermission)
|
||||||
{
|
{
|
||||||
<div class="row">
|
<div class="row mt-2">
|
||||||
<div class="col mt-2">
|
<div class="col">
|
||||||
@if (UploadMultiple)
|
@if (UploadMultiple)
|
||||||
{
|
{
|
||||||
<input type="file" id="@_fileinputid" name="file" accept="@_filter" multiple />
|
<input type="file" id="@_fileinputid" name="file" accept="@_filter" multiple />
|
||||||
@@ -52,17 +62,35 @@
|
|||||||
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="col mt-2 text-end">
|
<div class="col-auto">
|
||||||
<button type="button" class="btn btn-success" @onclick="UploadFile">@SharedLocalizer["Upload"]</button>
|
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
|
||||||
@if (ShowFiles && GetFileId() != -1)
|
@if (FileId != -1 && !UploadMultiple)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
|
<button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
@if (ShowProgress)
|
||||||
<div class="col mt-1"><span id="@_progressinfoid" style="display: none;"></span></div>
|
{
|
||||||
<div class="col text-center mt-1"><progress id="@_progressbarid" class="mt-1" style="display: none;"></progress></div>
|
<div class="row">
|
||||||
|
<div class="col mt-1"><span id="@_progressinfoid" style="display: none;"></span></div>
|
||||||
|
<div class="col mt-1"><progress id="@_progressbarid" class="mt-1" style="display: none;"></progress></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_uploading)
|
||||||
|
{
|
||||||
|
<div class="app-progress-indicator"></div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(_message))
|
||||||
|
{
|
||||||
|
<div class="row mt-1">
|
||||||
|
<div class="col">
|
||||||
|
<ModuleMessage Message="@_message" Type="@_messagetype" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -74,19 +102,11 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@if (!string.IsNullOrEmpty(_message))
|
|
||||||
{
|
|
||||||
<div class="row mt-1">
|
|
||||||
<div class="col">
|
|
||||||
<ModuleMessage Message="@_message" Type="@_messagetype" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _id;
|
private bool _initialized = false;
|
||||||
private List<Folder> _folders;
|
private List<Folder> _folders;
|
||||||
private List<File> _files = new List<File>();
|
private List<File> _files = new List<File>();
|
||||||
private string _fileinputid = string.Empty;
|
private string _fileinputid = string.Empty;
|
||||||
@@ -99,6 +119,7 @@
|
|||||||
private string _guid;
|
private string _guid;
|
||||||
private string _message = string.Empty;
|
private string _message = string.Empty;
|
||||||
private MessageType _messagetype;
|
private MessageType _messagetype;
|
||||||
|
private bool _uploading = false;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility
|
public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility
|
||||||
@@ -127,28 +148,45 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true
|
public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool ShowProgress { get; set; } = true; // optional - for indicating whether progress info should be displayed during upload - default is true
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false
|
public bool ShowSuccess { get; set; } = false; // optional - for indicating whether a success message should be displayed upon successful upload - default is false
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<int> OnSelectFolder { get; set; } // optional - executes a method in the calling component when a folder is selected
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<int> OnSelectFile { get; set; } // optional - executes a method in the calling component when a file is selected
|
||||||
|
|
||||||
|
[Obsolete("Use OnSelectFile instead.")]
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
|
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<int> OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted
|
public EventCallback<int> OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(Id))
|
// create unique id for component
|
||||||
{
|
_guid = Guid.NewGuid().ToString("N");
|
||||||
_id = Id;
|
_fileinputid = "FileInput_" + _guid;
|
||||||
}
|
_progressinfoid = "ProgressInfo_" + _guid;
|
||||||
|
_progressbarid = "ProgressBar_" + _guid;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
// packages folder is a framework folder for uploading installable nuget packages
|
// packages folder is a framework folder for uploading installable nuget packages
|
||||||
if (Folder == Constants.PackagesFolder)
|
if (Folder == Constants.PackagesFolder)
|
||||||
{
|
{
|
||||||
@@ -158,13 +196,6 @@
|
|||||||
ShowSuccess = true;
|
ShowSuccess = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ShowFiles)
|
|
||||||
{
|
|
||||||
ShowImage = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
|
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
|
||||||
{
|
{
|
||||||
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
|
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
|
||||||
@@ -175,24 +206,39 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
FolderId = -1;
|
FolderId = -1;
|
||||||
_message = "Folder Path " + Folder + "Does Not Exist";
|
_message = "Folder Path " + Folder + " Does Not Exist";
|
||||||
_messagetype = MessageType.Error;
|
_messagetype = MessageType.Error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ShowFolders)
|
||||||
|
{
|
||||||
|
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (FolderId != -1)
|
||||||
|
{
|
||||||
|
var folder = await FolderService.GetFolderAsync(FolderId);
|
||||||
|
if (folder != null)
|
||||||
|
{
|
||||||
|
_folders = new List<Folder> { folder };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (FileId != -1)
|
if (FileId != -1)
|
||||||
{
|
{
|
||||||
File file = await FileService.GetFileAsync(FileId);
|
File file = await FileService.GetFileAsync(FileId);
|
||||||
if (file != null)
|
if (file != null)
|
||||||
{
|
{
|
||||||
FolderId = file.FolderId;
|
FolderId = file.FolderId;
|
||||||
await OnSelect.InvokeAsync(FileId);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FileId = -1; // file does not exist
|
_message = "FileId " + FileId.ToString() + " Does Not Exist";
|
||||||
_message = "FileId " + FileId.ToString() + "Does Not Exist";
|
|
||||||
_messagetype = MessageType.Error;
|
_messagetype = MessageType.Error;
|
||||||
|
FileId = -1; // file does not exist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,11 +251,7 @@
|
|||||||
|
|
||||||
await GetFiles();
|
await GetFiles();
|
||||||
|
|
||||||
// create unique id for component
|
_initialized = true;
|
||||||
_guid = Guid.NewGuid().ToString("N");
|
|
||||||
_fileinputid = _guid + "FileInput";
|
|
||||||
_progressinfoid = _guid + "ProgressInfo";
|
|
||||||
_progressbarid = _guid + "ProgressBar";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GetFiles()
|
private async Task GetFiles()
|
||||||
@@ -225,26 +267,33 @@
|
|||||||
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
|
Folder folder = _folders.FirstOrDefault(item => item.FolderId == FolderId);
|
||||||
if (folder != null)
|
if (folder != null)
|
||||||
{
|
{
|
||||||
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.Permissions);
|
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, folder.PermissionList);
|
||||||
_files = await FileService.GetFilesAsync(FolderId);
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Browse, folder.PermissionList))
|
||||||
|
{
|
||||||
|
_files = await FileService.GetFilesAsync(FolderId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_files = new List<File>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_haseditpermission = false;
|
_haseditpermission = false;
|
||||||
_files = new List<File>();
|
_files = new List<File>();
|
||||||
}
|
}
|
||||||
}
|
if (_filter != "*")
|
||||||
if (_filter != "*")
|
|
||||||
{
|
|
||||||
List<File> filtered = new List<File>();
|
|
||||||
foreach (File file in _files)
|
|
||||||
{
|
{
|
||||||
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
|
List<File> filtered = new List<File>();
|
||||||
|
foreach (File file in _files)
|
||||||
{
|
{
|
||||||
filtered.Add(file);
|
if (_filter.ToUpper().IndexOf("." + file.Extension.ToUpper()) != -1)
|
||||||
|
{
|
||||||
|
filtered.Add(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_files = filtered;
|
||||||
}
|
}
|
||||||
_files = filtered;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,6 +307,8 @@
|
|||||||
FileId = -1;
|
FileId = -1;
|
||||||
_file = null;
|
_file = null;
|
||||||
_image = string.Empty;
|
_image = string.Empty;
|
||||||
|
|
||||||
|
await OnSelectFolder.InvokeAsync(FolderId);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -272,12 +323,11 @@
|
|||||||
{
|
{
|
||||||
_message = string.Empty;
|
_message = string.Empty;
|
||||||
FileId = int.Parse((string)e.Value);
|
FileId = int.Parse((string)e.Value);
|
||||||
if (FileId != -1)
|
|
||||||
{
|
|
||||||
await OnSelect.InvokeAsync(FileId);
|
|
||||||
}
|
|
||||||
|
|
||||||
await SetImage();
|
await SetImage();
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
await OnSelect.InvokeAsync(FileId);
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
await OnSelectFile.InvokeAsync(FileId);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,65 +348,135 @@
|
|||||||
var ratio = ratioX < ratioY ? ratioX : ratioY;
|
var ratio = ratioX < ratioY ? ratioX : ratioY;
|
||||||
|
|
||||||
_image = "<img src=\"" + _file.Url + "\" alt=\"" + _file.Name +
|
_image = "<img src=\"" + _file.Url + "\" alt=\"" + _file.Name +
|
||||||
"\" width=\"" + Convert.ToInt32(_file.ImageWidth * ratio).ToString() +
|
"\" width=\"" + Convert.ToInt32(_file.ImageWidth * ratio).ToString() +
|
||||||
"\" height=\"" + Convert.ToInt32(_file.ImageHeight * ratio).ToString() + "\" />";
|
"\" height=\"" + Convert.ToInt32(_file.ImageHeight * ratio).ToString() + "\" />";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UploadFile()
|
private async Task UploadFiles()
|
||||||
{
|
{
|
||||||
_message = string.Empty;
|
_message = string.Empty;
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
var upload = await interop.GetFiles(_fileinputid);
|
var uploads = await interop.GetFiles(_fileinputid);
|
||||||
if (upload.Length > 0)
|
|
||||||
|
if (uploads.Length > 0)
|
||||||
{
|
{
|
||||||
try
|
string restricted = "";
|
||||||
|
foreach (var upload in uploads)
|
||||||
{
|
{
|
||||||
string result;
|
var filename = upload.Split(':')[0];
|
||||||
if (Folder == Constants.PackagesFolder)
|
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
|
||||||
|
if (!PageState.Site.UploadableFiles.Split(',').Contains(extension.ToLower()))
|
||||||
{
|
{
|
||||||
result = await FileService.UploadFilesAsync(Folder, upload, _guid);
|
restricted += (restricted == "" ? "" : ",") + extension;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = await FileService.UploadFilesAsync(FolderId, upload, _guid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == string.Empty)
|
|
||||||
{
|
|
||||||
await logger.LogInformation("File Upload Succeeded {Files}", upload);
|
|
||||||
if (ShowSuccess)
|
|
||||||
{
|
|
||||||
_message = Localizer["Success.File.Upload"];
|
|
||||||
_messagetype = MessageType.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set FileId to first file in upload collection
|
|
||||||
await GetFiles();
|
|
||||||
var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault();
|
|
||||||
if (file != null)
|
|
||||||
{
|
|
||||||
FileId = file.FileId;
|
|
||||||
await SetImage();
|
|
||||||
await OnUpload.InvokeAsync(FileId);
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await logger.LogError("File Upload Failed For {Files}", result.Replace(",", ", "));
|
|
||||||
|
|
||||||
_message = Localizer["Error.File.Upload"];
|
|
||||||
_messagetype = MessageType.Error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
if (restricted == "")
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
|
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
_message = Localizer["Error.File.Upload"];
|
try
|
||||||
_messagetype = MessageType.Error;
|
{
|
||||||
|
// upload the files
|
||||||
|
var posturl = Utilities.TenantUrl(PageState.Alias, "/api/file/upload");
|
||||||
|
var folder = (Folder == Constants.PackagesFolder) ? Folder : FolderId.ToString();
|
||||||
|
var jwt = "";
|
||||||
|
if (PageState.Runtime == Shared.Runtime.Hybrid)
|
||||||
|
{
|
||||||
|
jwt = await UserService.GetTokenAsync();
|
||||||
|
if (string.IsNullOrEmpty(jwt))
|
||||||
|
{
|
||||||
|
await logger.LogInformation("File Upload Failed From .NET MAUI Due To Missing Security Token. Token Options Must Be Set In User Settings.");
|
||||||
|
_message = "Security Token Not Specified";
|
||||||
|
_messagetype = MessageType.Error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
await interop.SetElementAttribute(_progressinfoid, "style", "display: none;");
|
||||||
|
await interop.SetElementAttribute(_progressbarid, "style", "display: none;");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_uploading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
await logger.LogInformation("File Upload Succeeded {Files}", uploads);
|
||||||
|
if (ShowSuccess)
|
||||||
|
{
|
||||||
|
_message = Localizer["Success.File.Upload"];
|
||||||
|
_messagetype = MessageType.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await logger.LogError("File Upload Failed {Files}", uploads);
|
||||||
|
_message = Localizer["Error.File.Upload"];
|
||||||
|
_messagetype = MessageType.Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Folder == Constants.PackagesFolder)
|
||||||
|
{
|
||||||
|
await OnUpload.InvokeAsync(-1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// set FileId to first file in upload collection
|
||||||
|
var file = await FileService.GetFileAsync(int.Parse(folder), uploads[0].Split(":")[0]);
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
FileId = file.FileId;
|
||||||
|
await SetImage();
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
await OnSelect.InvokeAsync(FileId);
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
await OnSelectFile.InvokeAsync(FileId);
|
||||||
|
await OnUpload.InvokeAsync(FileId);
|
||||||
|
}
|
||||||
|
await GetFiles();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
|
||||||
|
_message = Localizer["Error.File.Upload"];
|
||||||
|
_messagetype = MessageType.Error;
|
||||||
|
_uploading = false;
|
||||||
|
await tokenSource.CancelAsync();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
tokenSource.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
|
||||||
|
_messagetype = MessageType.Warning;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -375,26 +495,53 @@
|
|||||||
await logger.LogInformation("File Deleted {File}", FileId);
|
await logger.LogInformation("File Deleted {File}", FileId);
|
||||||
await OnDelete.InvokeAsync(FileId);
|
await OnDelete.InvokeAsync(FileId);
|
||||||
|
|
||||||
_message = Localizer["Success.File.Delete"];
|
if (ShowSuccess)
|
||||||
_messagetype = MessageType.Success;
|
{
|
||||||
|
_message = Localizer["Success.File.Delete"];
|
||||||
|
_messagetype = MessageType.Success;
|
||||||
|
}
|
||||||
|
|
||||||
await GetFiles();
|
await GetFiles();
|
||||||
FileId = -1;
|
FileId = -1;
|
||||||
await SetImage();
|
await SetImage();
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
await OnSelect.InvokeAsync(FileId);
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
await OnSelectFile.InvokeAsync(FileId);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message);
|
await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message);
|
||||||
|
|
||||||
_message = Localizer["Error.File.Delete"];
|
_message = Localizer["Error.File.Delete"];
|
||||||
_messagetype = MessageType.Error;
|
_messagetype = MessageType.Error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetFileId() => FileId;
|
public int GetFileId() => FileId;
|
||||||
|
|
||||||
public int GetFolderId() => FolderId;
|
public int GetFolderId() => FolderId;
|
||||||
|
|
||||||
public File GetFile() => _file;
|
public File GetFile() => _file;
|
||||||
|
|
||||||
|
public async Task Refresh()
|
||||||
|
{
|
||||||
|
await Refresh(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Refresh(int fileId)
|
||||||
|
{
|
||||||
|
await GetFiles();
|
||||||
|
if (fileId != -1)
|
||||||
|
{
|
||||||
|
var file = _files.Where(item => item.FileId == fileId).FirstOrDefault();
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
FileId = file.FileId;
|
||||||
|
await SetImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
47
Oqtane.Client/Modules/Controls/InputList.razor
Normal file
47
Oqtane.Client/Modules/Controls/InputList.razor
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
@namespace Oqtane.Modules.Controls
|
||||||
|
@inherits LocalizableComponent
|
||||||
|
|
||||||
|
<input type="text" value="@Value" list="@_id" class="form-control" @onchange="(e => OnChange(e))" />
|
||||||
|
<datalist id="@_id" value="@Value">
|
||||||
|
@foreach(var kvp in DataList)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(kvp.Value))
|
||||||
|
{
|
||||||
|
<option value="@kvp.Key">@Localize(kvp.Value, kvp.Value)</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<option value="@kvp.Key">@Localize(kvp.Key, kvp.Key)</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</datalist>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string _id;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Value { get; set; }
|
||||||
|
|
||||||
|
[EditorRequired]
|
||||||
|
[Parameter]
|
||||||
|
public Dictionary<string, string> DataList { get; set; }
|
||||||
|
|
||||||
|
[EditorRequired]
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<string> ValueChanged { get; set; }
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
// create unique id for component
|
||||||
|
_id = "DataList_" + Guid.NewGuid().ToString("N");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnChange(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
Value = e.Value.ToString();
|
||||||
|
if (ValueChanged.HasDelegate)
|
||||||
|
{
|
||||||
|
ValueChanged.InvokeAsync(Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using Oqtane.Shared;
|
|
||||||
|
|
||||||
namespace Oqtane.Modules.Controls
|
namespace Oqtane.Modules.Controls
|
||||||
{
|
{
|
||||||
public class LocalizableComponent : ModuleControlBase
|
public class LocalizableComponent : ModuleControlBase
|
||||||
{
|
{
|
||||||
|
[Inject] public IStringLocalizerFactory LocalizerFactory { get; set; }
|
||||||
|
|
||||||
private IStringLocalizer _localizer;
|
private IStringLocalizer _localizer;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
@@ -30,22 +30,15 @@ namespace Oqtane.Modules.Controls
|
|||||||
var key = $"{ResourceKey}.{propertyName}";
|
var key = $"{ResourceKey}.{propertyName}";
|
||||||
var value = Localize(key);
|
var value = Localize(key);
|
||||||
|
|
||||||
if (value == key)
|
if (value == key || value == String.Empty)
|
||||||
{
|
{
|
||||||
// Returns default property value (English version) instead of ResourceKey.PropertyName
|
// return default property value if key does not exist in resource file or value is empty
|
||||||
return propertyValue;
|
return propertyValue;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (value == String.Empty)
|
// return localized value
|
||||||
{
|
return value;
|
||||||
// Returns default property value (English version)
|
|
||||||
return propertyValue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,24 +46,15 @@ namespace Oqtane.Modules.Controls
|
|||||||
{
|
{
|
||||||
IsLocalizable = false;
|
IsLocalizable = false;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(ResourceType))
|
if (String.IsNullOrEmpty(ResourceType))
|
||||||
{
|
{
|
||||||
ResourceType = ModuleState?.ModuleType;
|
ResourceType = ModuleState?.ModuleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!String.IsNullOrEmpty(ResourceKey) && !string.IsNullOrEmpty(ResourceType))
|
if (!String.IsNullOrEmpty(ResourceKey) && !String.IsNullOrEmpty(ResourceType))
|
||||||
{
|
{
|
||||||
var moduleType = Type.GetType(ResourceType);
|
_localizer = LocalizerFactory.Create(ResourceType);
|
||||||
if (moduleType != null)
|
IsLocalizable = true;
|
||||||
{
|
|
||||||
using (var scope = ServiceActivator.GetScope())
|
|
||||||
{
|
|
||||||
var localizerFactory = scope.ServiceProvider.GetService<IStringLocalizerFactory>();
|
|
||||||
_localizer = localizerFactory.Create(moduleType);
|
|
||||||
|
|
||||||
IsLocalizable = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,25 @@
|
|||||||
@inherits ModuleControlBase
|
@inherits ModuleControlBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
@if (!string.IsNullOrEmpty(_message))
|
@if (!string.IsNullOrEmpty(Message))
|
||||||
{
|
{
|
||||||
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
|
<div class="@_classname alert-dismissible fade show mb-3" role="alert">
|
||||||
@((MarkupString)_message)
|
@((MarkupString)Message)
|
||||||
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
@((MarkupString)" ")<NavLink href="@NavigateUrl("admin/log")">View Details</NavLink>
|
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
|
||||||
|
}
|
||||||
|
@if (ModuleState != null)
|
||||||
|
{
|
||||||
|
@if (ModuleState.RenderMode == RenderModes.Static)
|
||||||
|
{
|
||||||
|
<a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
<button type="button" class="btn-close" aria-label="Close" @onclick="DismissModal"></button>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +34,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public MessageType Type { get; set; }
|
public MessageType Type { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public RenderModeBoundary Parent { get; set; }
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
_message = Message;
|
_message = Message;
|
||||||
@@ -54,10 +67,15 @@
|
|||||||
|
|
||||||
return classname;
|
return classname;
|
||||||
}
|
}
|
||||||
|
private void CloseMessage(MouseEventArgs e)
|
||||||
private void DismissModal()
|
|
||||||
{
|
{
|
||||||
_message = "";
|
if(Parent != null)
|
||||||
StateHasChanged();
|
{
|
||||||
|
Parent.DismissMessage();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(NavigationManager.Uri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,96 +1,179 @@
|
|||||||
@namespace Oqtane.Modules.Controls
|
@namespace Oqtane.Modules.Controls
|
||||||
@inherits ModuleControlBase
|
@inherits ModuleControlBase
|
||||||
|
@inject IStringLocalizerFactory LocalizerFactory
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
@typeparam TableItem
|
@typeparam TableItem
|
||||||
|
|
||||||
@if (ItemList != null)
|
@if (ItemList != null)
|
||||||
{
|
{
|
||||||
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
|
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
|
||||||
{
|
{
|
||||||
<ul class="pagination justify-content-center my-2">
|
@if (!string.IsNullOrEmpty(SearchProperties))
|
||||||
<li class="page-item@((_page > 1) ? "" : " disabled")">
|
{
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
<form autocomplete="off">
|
||||||
</li>
|
<div class="input-group my-3 @SearchBoxClass">
|
||||||
@if (_pages > _displayPages && _displayPages > 1)
|
<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>
|
||||||
<li class="page-item@((_page > _displayPages) ? "" : " disabled")">
|
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
|
||||||
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></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" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
}
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
<li class="page-item@((_page > 1) ? "" : " 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 active">
|
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
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">
|
var pager = i;
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
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>
|
||||||
}
|
}
|
||||||
}
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
<li class="page-item@((_page < _pages) ? "" : " 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>
|
||||||
<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) ? "" : " 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>
|
||||||
}
|
<li class="page-item disabled">
|
||||||
<li class="page-item@((_page < _pages) ? "" : " disabled")">
|
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
<li class="page-item disabled">
|
}
|
||||||
<a class="page-link" style="white-space: nowrap;">Page @_page of @_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 (Format == "Table" && Row != null)
|
@if (Format == "Table" && Row != null)
|
||||||
{
|
{
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="@Class">
|
<table class="@Class">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="@RowClass">@Header</tr>
|
<tr class="@RowClass">@Header</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (var item in ItemList)
|
@foreach (var item in ItemList)
|
||||||
{
|
{
|
||||||
<tr class="@RowClass">@Row(item)</tr>
|
<tr class="@RowClass">@Row(item)</tr>
|
||||||
@if (Detail != null)
|
@if (Detail != null)
|
||||||
{
|
{
|
||||||
<tr>@Detail(item)</tr>
|
<tr>@Detail(item)</tr>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
<tfoot>
|
||||||
</div>
|
<tr class="@RowClass">@Footer</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
@if (Format == "Grid" && Row != null)
|
@if (Format == "Grid" && Row != null)
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
int rows = 0;
|
int rows = 0;
|
||||||
int cols = 0;
|
int cols = 0;
|
||||||
if (ItemList != null)
|
if (ItemList != null)
|
||||||
{
|
{
|
||||||
if (_columns == 0)
|
if (_columns == 0)
|
||||||
{
|
{
|
||||||
count = ItemList.Count();
|
count = ItemList.Count();
|
||||||
rows = 1;
|
rows = 1;
|
||||||
cols = count;
|
cols = count;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
count = (int)Math.Ceiling(ItemList.Count() / (decimal)_columns) * _columns;
|
count = (int)Math.Ceiling(ItemList.Count() / (decimal)_columns) * _columns;
|
||||||
rows = count / _columns;
|
rows = count / _columns;
|
||||||
cols = _columns;
|
cols = _columns;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<div class="@Class">
|
<div class="@Class">
|
||||||
@for (int row = 0; row < rows; row++)
|
@for (int row = 0; row < rows; row++)
|
||||||
@@ -112,57 +195,111 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if ((Toolbar == "Bottom" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
|
|
||||||
|
@if ((Toolbar == "Bottom" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
|
||||||
{
|
{
|
||||||
<ul class="pagination justify-content-center my-2">
|
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
|
||||||
<li class="page-item@((_page > 1) ? "" : " disabled")">
|
{
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
<ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
|
||||||
</li>
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
@if (_pages > _displayPages && _displayPages > 1)
|
<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 class="page-item@((_page > _displayPages) ? "" : " 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>
|
||||||
}
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
<li class="page-item@((_page > 1) ? "" : " 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 active">
|
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
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">
|
var pager = i;
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
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>
|
||||||
}
|
}
|
||||||
}
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
<li class="page-item@((_page < _pages) ? "" : " 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>
|
||||||
<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) ? "" : " 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>
|
||||||
}
|
<li class="page-item disabled">
|
||||||
<li class="page-item@((_page < _pages) ? "" : " disabled")">
|
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||||
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
<li class="page-item disabled">
|
}
|
||||||
<a class="page-link" style="white-space: nowrap;">Page @_page of @_pages</a>
|
else
|
||||||
</li>
|
{
|
||||||
</ul>
|
<ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
|
||||||
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
|
{
|
||||||
|
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
@for (int i = _startPage; i <= _endPage; i++)
|
||||||
|
{
|
||||||
|
var pager = i;
|
||||||
|
if (pager == _page)
|
||||||
|
{
|
||||||
|
<li class="page-item app-pager-pointer active">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<li class="page-item app-pager-pointer">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
|
{
|
||||||
|
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private IStringLocalizer Localizer;
|
||||||
private int _pages = 0;
|
private int _pages = 0;
|
||||||
private int _page = 1;
|
private int _page = 1;
|
||||||
private int _maxItems = 10;
|
private int _maxItems = 10;
|
||||||
@@ -170,6 +307,9 @@
|
|||||||
private int _startPage = 0;
|
private int _startPage = 0;
|
||||||
private int _endPage = 0;
|
private int _endPage = 0;
|
||||||
private int _columns = 0;
|
private int _columns = 0;
|
||||||
|
private string _search = "";
|
||||||
|
|
||||||
|
private IEnumerable<TableItem> AllItems;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Format { get; set; } // Table or Grid
|
public string Format { get; set; } // Table or Grid
|
||||||
@@ -183,6 +323,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public RenderFragment<TableItem> Row { get; set; } = null; // required
|
public RenderFragment<TableItem> Row { get; set; } = null; // required
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public RenderFragment Footer { get; set; } = null; // only applicable to Table layouts
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts
|
public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts
|
||||||
|
|
||||||
@@ -207,14 +350,37 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string RowClass { get; set; } // class for row element - ie. <tr> for Table or <div> for Grid
|
public string RowClass { get; set; } // class for row element - ie. <tr> for Table or <div> for Grid
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string ColumnClass { get; set; } // class for column element - only applicable to Grid format
|
public string ColumnClass { get; set; } // class for column element - only applicable to Grid format
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Action<int> OnPageChange { get; set; } // a method to be executed in the calling component when the page changes
|
public Action<int> OnPageChange { get; set; } // a method to be executed in the calling component when the page changes
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string SearchProperties { get; set; } // comma delimited list of property names to include in search
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string SearchBoxClass { get; set; } // class for Search box
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Parameters { get; set; } // optional - querystring parameters in the form of "id=x&name=y" used in static render mode
|
||||||
|
|
||||||
|
[SupplyParameterFromForm(FormName = "PagerForm")]
|
||||||
|
public string _Search { get => ""; set => _search = value; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Accepted values are Start or Center or End. The default value is Center
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public string PaginationAlignment { get; set; } = "center"; // Alignment of the Page Numbering start, center, end
|
||||||
|
|
||||||
private IEnumerable<TableItem> ItemList { get; set; }
|
private IEnumerable<TableItem> ItemList { get; set; }
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
Localizer = LocalizerFactory.Create(GetType().FullName);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(Format))
|
if (string.IsNullOrEmpty(Format))
|
||||||
@@ -263,7 +429,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(PageSize))
|
if (PageState.QueryString.ContainsKey("search"))
|
||||||
|
{
|
||||||
|
_search = PageState.QueryString["search"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(SearchProperties))
|
||||||
|
{
|
||||||
|
AllItems = Items; // only used in search
|
||||||
|
if (!string.IsNullOrEmpty(_search))
|
||||||
|
{
|
||||||
|
Search();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(PageSize))
|
||||||
{
|
{
|
||||||
_maxItems = int.Parse(PageSize);
|
_maxItems = int.Parse(PageSize);
|
||||||
}
|
}
|
||||||
@@ -278,14 +458,22 @@
|
|||||||
_displayPages = int.Parse(DisplayPages);
|
_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
|
else
|
||||||
{
|
{
|
||||||
_page = 1;
|
if (!string.IsNullOrEmpty(CurrentPage))
|
||||||
|
{
|
||||||
|
_page = int.Parse(CurrentPage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_page = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (_page < 1) _page = 1;
|
||||||
|
|
||||||
_startPage = 0;
|
_startPage = 0;
|
||||||
_endPage = 0;
|
_endPage = 0;
|
||||||
@@ -297,7 +485,6 @@
|
|||||||
{
|
{
|
||||||
_page = _pages;
|
_page = _pages;
|
||||||
}
|
}
|
||||||
ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
|
|
||||||
SetPagerSize();
|
SetPagerSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,13 +497,13 @@
|
|||||||
{
|
{
|
||||||
_endPage = _pages;
|
_endPage = _pages;
|
||||||
}
|
}
|
||||||
OnPageChange?.Invoke(_page);
|
ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
OnPageChange?.Invoke(_page);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateList(int page)
|
public void UpdateList(int page)
|
||||||
{
|
{
|
||||||
ItemList = Items.Skip((page - 1) * _maxItems).Take(_maxItems);
|
|
||||||
_page = page;
|
_page = page;
|
||||||
SetPagerSize();
|
SetPagerSize();
|
||||||
}
|
}
|
||||||
@@ -356,4 +543,96 @@
|
|||||||
|
|
||||||
UpdateList(_page);
|
UpdateList(_page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Search()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_search))
|
||||||
|
{
|
||||||
|
Items = AllItems.Where(item =>
|
||||||
|
{
|
||||||
|
var values = SearchProperties.Split(',')
|
||||||
|
.Select(itemType => GetPropertyValue(item, itemType))
|
||||||
|
.Where(value => value != null)
|
||||||
|
.Select(value => value.ToString().ToLower());
|
||||||
|
|
||||||
|
return values.Any(value => value.Contains(_search.ToLower()));
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Items = AllItems;
|
||||||
|
}
|
||||||
|
_pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems);
|
||||||
|
UpdateList(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private object GetPropertyValue(object obj, string propertyName)
|
||||||
|
{
|
||||||
|
var index = propertyName.IndexOf(".");
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
var propertyInfo = obj.GetType().GetProperty(propertyName.Substring(0, index));
|
||||||
|
if (propertyInfo != null)
|
||||||
|
{
|
||||||
|
return GetPropertyValue(propertyInfo.GetValue(obj), propertyName.Substring(index + 1));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var propertyInfo = obj.GetType().GetProperty(propertyName);
|
||||||
|
if (propertyInfo != null)
|
||||||
|
{
|
||||||
|
return propertyInfo.GetValue(obj);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_search = "";
|
||||||
|
Items = AllItems;
|
||||||
|
_pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems);
|
||||||
|
UpdateList(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatSearchProperties()
|
||||||
|
{
|
||||||
|
var properties = new List<string>();
|
||||||
|
foreach (var property in SearchProperties.Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
var index = property.LastIndexOf(".");
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
properties.Add(property.Substring(index + 1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
properties.Add(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string.Join(",", properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string PageUrl(int page, string search)
|
||||||
|
{
|
||||||
|
var parameters = new Dictionary<string, string>(PageState.QueryString);
|
||||||
|
if (parameters.ContainsKey("page")) parameters.Remove("page");
|
||||||
|
parameters.Add("page", page.ToString());
|
||||||
|
if (parameters.ContainsKey("search")) parameters.Remove("search");
|
||||||
|
if (!string.IsNullOrEmpty(search))
|
||||||
|
{
|
||||||
|
parameters.Add("search", search);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(Parameters))
|
||||||
|
{
|
||||||
|
foreach (var parameter in Utilities.ParseQueryString(Parameters))
|
||||||
|
{
|
||||||
|
if (parameters.ContainsKey(parameter.Key)) parameters.Remove(parameter.Key);
|
||||||
|
parameters.Add(parameter.Key, parameter.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return PageState.Route.AbsolutePath + Utilities.CreateQueryString(parameters);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
@namespace Oqtane.Modules.Controls
|
@namespace Oqtane.Modules.Controls
|
||||||
|
@using System.Text.Json
|
||||||
@inherits ModuleControlBase
|
@inherits ModuleControlBase
|
||||||
@inject IRoleService RoleService
|
@inject IRoleService RoleService
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
|
@inject IUserRoleService UserRoleService
|
||||||
@inject IStringLocalizer<PermissionGrid> Localizer
|
@inject IStringLocalizer<PermissionGrid> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@@ -14,20 +16,19 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">@Localizer["Role"]</th>
|
<th scope="col">@Localizer["Role"]</th>
|
||||||
@foreach (PermissionString permission in _permissions)
|
@foreach (var permissionname in _permissionnames)
|
||||||
{
|
{
|
||||||
<th style="text-align: center; width: 1px;">@Localizer[permission.PermissionName]</th>
|
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
@foreach (Role role in _roles)
|
@foreach (Role role in _roles)
|
||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td>@role.Name</td>
|
<td>@role.Name</td>
|
||||||
@foreach (PermissionString permission in _permissions)
|
@foreach (var permissionname in _permissionnames)
|
||||||
{
|
{
|
||||||
var p = permission;
|
|
||||||
<td style="text-align: center;">
|
<td style="text-align: center;">
|
||||||
<TriStateCheckBox Value=@GetPermissionValue(p.Permissions, role.Name) Disabled=@GetPermissionDisabled(role.Name) OnChange="@(e => PermissionChanged(e, p.PermissionName, role.Name))" />
|
<TriStateCheckBox Value=@GetPermissionValue(permissionname, role.Name, -1) Disabled="@GetPermissionDisabled(permissionname, role.Name)" OnChange="@(e => PermissionChanged(e, permissionname, role.Name, -1))" />
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
@@ -49,23 +50,21 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">@Localizer["User"]</th>
|
<th scope="col">@Localizer["User"]</th>
|
||||||
@foreach (PermissionString permission in _permissions)
|
@foreach (var permissionname in _permissionnames)
|
||||||
{
|
{
|
||||||
<th style="text-align: center; width: 1px;">@Localizer[permission.PermissionName]</th>
|
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (User user in _users)
|
@foreach (User user in _users)
|
||||||
{
|
{
|
||||||
string userid = "[" + user.UserId.ToString() + "]";
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>@user.DisplayName</td>
|
<td>@user.DisplayName (@user.Username)</td>
|
||||||
@foreach (PermissionString permission in _permissions)
|
@foreach (var permissionname in _permissionnames)
|
||||||
{
|
{
|
||||||
var p = permission;
|
|
||||||
<td style="text-align: center; width: 1px;">
|
<td style="text-align: center; width: 1px;">
|
||||||
<TriStateCheckBox Value=@GetPermissionValue(p.Permissions, userid) Disabled=false OnChange="@(e => PermissionChanged(e, p.PermissionName, userid))" />
|
<TriStateCheckBox Value=@GetPermissionValue(permissionname, "", user.UserId) Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" />
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
@@ -77,34 +76,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col-11">
|
||||||
<table class="table table-borderless">
|
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="_user" />
|
||||||
<tbody>
|
</div>
|
||||||
<tr>
|
<div class="col-1">
|
||||||
<td class="input-group">
|
<button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
|
||||||
<input type="text" name="Username" class="form-control" placeholder="@Localizer["Username.Enter"]" @bind="@_username" />
|
|
||||||
<button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<br />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<ModuleMessage Type="MessageType.Error" Message="@_message" />
|
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _permissionnames = string.Empty;
|
private List<string> _permissionnames;
|
||||||
|
private List<Permission> _permissions;
|
||||||
private List<Role> _roles;
|
private List<Role> _roles;
|
||||||
private List<PermissionString> _permissions;
|
|
||||||
private List<User> _users = new List<User>();
|
private List<User> _users = new List<User>();
|
||||||
private string _username = string.Empty;
|
private AutoComplete _user;
|
||||||
private string _message = string.Empty;
|
private string _message = string.Empty;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
@@ -114,159 +106,230 @@
|
|||||||
public string PermissionNames { get; set; }
|
public string PermissionNames { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Permissions { get; set; }
|
public string Permissions { get; set; } // deprecated - use PermissionList instead
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
[Parameter]
|
||||||
|
public List<Permission> PermissionList { get; set; }
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
if (!string.IsNullOrEmpty(Permissions))
|
||||||
|
{
|
||||||
|
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
|
||||||
|
if (!UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
_roles.RemoveAll(item => item.Name == RoleNames.Host);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get permission names
|
||||||
if (string.IsNullOrEmpty(PermissionNames))
|
if (string.IsNullOrEmpty(PermissionNames))
|
||||||
{
|
{
|
||||||
_permissionnames = Shared.PermissionNames.View + "," + Shared.PermissionNames.Edit;
|
_permissionnames = new List<string>();
|
||||||
|
_permissionnames.Add(Shared.PermissionNames.View);
|
||||||
|
_permissionnames.Add(Shared.PermissionNames.Edit);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_permissionnames = PermissionNames;
|
_permissionnames = PermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId);
|
// initialize permissions
|
||||||
_roles.Insert(0, new Role { Name = RoleNames.Everyone });
|
_permissions = new List<Permission>();
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (PermissionList != null && PermissionList.Any())
|
||||||
{
|
{
|
||||||
_roles.Add(new Role { Name = RoleNames.Host });
|
foreach (var permission in PermissionList)
|
||||||
}
|
|
||||||
|
|
||||||
_permissions = new List<PermissionString>();
|
|
||||||
|
|
||||||
foreach (string permissionname in _permissionnames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
|
||||||
{
|
|
||||||
// initialize with admin role
|
|
||||||
_permissions.Add(new PermissionString { PermissionName = permissionname, Permissions = RoleNames.Admin });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Permissions))
|
|
||||||
{
|
|
||||||
// populate permissions
|
|
||||||
foreach (PermissionString permissionstring in UserSecurity.GetPermissionStrings(Permissions))
|
|
||||||
{
|
{
|
||||||
if (_permissions.Find(item => item.PermissionName == permissionstring.PermissionName) != null)
|
_permissions.Add(permission);
|
||||||
|
if (permission.UserId != null)
|
||||||
{
|
{
|
||||||
_permissions[_permissions.FindIndex(item => item.PermissionName == permissionstring.PermissionName)].Permissions = permissionstring.Permissions;
|
if (!_users.Any(item => item.UserId == permission.UserId.Value))
|
||||||
}
|
|
||||||
|
|
||||||
if (permissionstring.Permissions.Contains("["))
|
|
||||||
{
|
|
||||||
foreach (string user in permissionstring.Permissions.Split(new char[] { '[' }, StringSplitOptions.RemoveEmptyEntries))
|
|
||||||
{
|
{
|
||||||
if (user.Contains("]"))
|
_users.Add(await UserService.GetUserAsync(permission.UserId.Value, ModuleState.SiteId));
|
||||||
{
|
|
||||||
var userid = int.Parse(user.Substring(0, user.IndexOf("]")));
|
|
||||||
if (_users.Where(item => item.UserId == userid).FirstOrDefault() == null)
|
|
||||||
{
|
|
||||||
_users.Add(await UserService.GetUserAsync(userid, ModuleState.SiteId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (string permissionname in _permissionnames)
|
||||||
|
{
|
||||||
|
// permission names can be in the form of "EntityName:PermissionName:Roles"
|
||||||
|
if (permissionname.Contains(":"))
|
||||||
|
{
|
||||||
|
var segments = permissionname.Split(':');
|
||||||
|
if (segments.Length == 3)
|
||||||
|
{
|
||||||
|
foreach (var role in segments[2].Split(';'))
|
||||||
|
{
|
||||||
|
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], role, null, true));
|
||||||
|
}
|
||||||
|
// ensure admin access
|
||||||
|
if (!_permissions.Any(item => item.EntityName == segments[0] && item.PermissionName == segments[1] && item.RoleName == RoleNames.Admin))
|
||||||
|
{
|
||||||
|
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], RoleNames.Admin, null, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_permissions.Add(new Permission(ModuleState.SiteId, EntityName, permissionname, RoleNames.Admin, null, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool? GetPermissionValue(string permissions, string securityKey)
|
private string GetPermissionName(string permissionName)
|
||||||
{
|
{
|
||||||
if ((";" + permissions + ";").Contains(";" + "!" + securityKey + ";"))
|
return (permissionName.Contains(":")) ? permissionName.Split(':')[1] : permissionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetEntityName(string permissionName)
|
||||||
|
{
|
||||||
|
return (permissionName.Contains(":")) ? permissionName.Split(':')[0] : EntityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DisplayPermissionName(string permissionName)
|
||||||
|
{
|
||||||
|
var name = Localizer[GetPermissionName(permissionName)].ToString();
|
||||||
|
name += " " + Localizer[GetEntityName(permissionName)].ToString();
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool? GetPermissionValue(string permissionName, string roleName, int userId)
|
||||||
|
{
|
||||||
|
bool? isauthorized = null;
|
||||||
|
if (roleName != "")
|
||||||
{
|
{
|
||||||
return false; // deny permission
|
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
|
||||||
|
if (permission != null)
|
||||||
|
{
|
||||||
|
isauthorized = permission.IsAuthorized;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if ((";" + permissions + ";").Contains(";" + securityKey + ";"))
|
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
|
||||||
|
if (permission != null)
|
||||||
{
|
{
|
||||||
return true; // grant permission
|
isauthorized = permission.IsAuthorized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isauthorized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool GetPermissionDisabled(string permissionName, string roleName)
|
||||||
|
{
|
||||||
|
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return null; // not specified
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool GetPermissionDisabled(string roleName)
|
private void PermissionChanged(bool? value, string permissionName, string roleName, int userId)
|
||||||
=> (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) ? true : false;
|
{
|
||||||
|
if (roleName != "")
|
||||||
|
{
|
||||||
|
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
|
||||||
|
if (permission != null)
|
||||||
|
{
|
||||||
|
_permissions.Remove(permission);
|
||||||
|
}
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
|
||||||
|
if (permission != null)
|
||||||
|
{
|
||||||
|
_permissions.Remove(permission);
|
||||||
|
}
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Dictionary<string, string>> GetUsers(string filter)
|
||||||
|
{
|
||||||
|
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||||
|
return users.Where(item => item.User.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase) || item.User.Username.Contains(filter, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToDictionary(item => item.UserId.ToString(), item => item.User.DisplayName + " (" + item.User.Username + ")");
|
||||||
|
}
|
||||||
|
|
||||||
private async Task AddUser()
|
private async Task AddUser()
|
||||||
{
|
{
|
||||||
if (_users.Where(item => item.Username == _username).FirstOrDefault() == null)
|
if (!string.IsNullOrEmpty(_user.Key))
|
||||||
{
|
{
|
||||||
try
|
var user = await UserService.GetUserAsync(int.Parse(_user.Key), ModuleState.SiteId);
|
||||||
|
if (user != null && !_users.Any(item => item.UserId == user.UserId))
|
||||||
{
|
{
|
||||||
var user = await UserService.GetUserAsync(_username, ModuleState.SiteId);
|
_users.Add(user);
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
_users.Add(user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_message = Localizer["Message.Username.DontExist"];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
_username = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PermissionChanged(bool? value, string permissionName, string securityId)
|
|
||||||
{
|
|
||||||
var selected = value;
|
|
||||||
var permission = _permissions.Find(item => item.PermissionName == permissionName);
|
|
||||||
if (permission != null)
|
|
||||||
{
|
{
|
||||||
var ids = permission.Permissions.Split(';').ToList();
|
_message = Localizer["Message.Username.DontExist"];
|
||||||
|
|
||||||
ids.Remove(securityId); // remove grant permission
|
|
||||||
ids.Remove("!" + securityId); // remove deny permission
|
|
||||||
|
|
||||||
switch (selected)
|
|
||||||
{
|
|
||||||
case true:
|
|
||||||
ids.Add(securityId); // add grant permission
|
|
||||||
break;
|
|
||||||
case false:
|
|
||||||
ids.Add("!" + securityId); // add deny permission
|
|
||||||
break;
|
|
||||||
case null:
|
|
||||||
break; // permission not specified
|
|
||||||
}
|
|
||||||
|
|
||||||
_permissions[_permissions.FindIndex(item => item.PermissionName == permissionName)].Permissions = string.Join(";", ids.ToArray());
|
|
||||||
}
|
}
|
||||||
|
_user.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetPermissions()
|
public string GetPermissions()
|
||||||
{
|
{
|
||||||
ValidatePermissions();
|
ValidatePermissions();
|
||||||
return UserSecurity.SetPermissionStrings(_permissions);
|
return JsonSerializer.Serialize(_permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Permission> GetPermissionList()
|
||||||
|
{
|
||||||
|
ValidatePermissions();
|
||||||
|
return _permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidatePermissions()
|
private void ValidatePermissions()
|
||||||
{
|
{
|
||||||
PermissionString permission;
|
// remove deny all users, unauthenticated, and registered users
|
||||||
for (int i = 0; i < _permissions.Count; i++)
|
var permissions = _permissions.Where(item => !item.IsAuthorized &&
|
||||||
|
(item.RoleName == RoleNames.Everyone || item.RoleName == RoleNames.Unauthenticated || item.RoleName == RoleNames.Registered)).ToList();
|
||||||
|
foreach (var permission in permissions)
|
||||||
{
|
{
|
||||||
permission = _permissions[i];
|
_permissions.Remove(permission);
|
||||||
List<string> ids = permission.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries).ToList();
|
}
|
||||||
ids.Remove("!" + RoleNames.Everyone); // remove deny all users
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
ids.Remove("!" + RoleNames.Registered); // remove deny registered users
|
{
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
// remove deny administrators and host users
|
||||||
|
permissions = _permissions.Where(item => !item.IsAuthorized &&
|
||||||
|
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)).ToList();
|
||||||
|
foreach (var permission in permissions)
|
||||||
{
|
{
|
||||||
ids.Remove("!" + RoleNames.Admin); // remove deny administrators
|
_permissions.Remove(permission);
|
||||||
ids.Remove("!" + RoleNames.Host); // remove deny host users
|
}
|
||||||
if (!ids.Contains(RoleNames.Host) && !ids.Contains(RoleNames.Admin))
|
foreach (var permissionname in _permissionnames)
|
||||||
|
{
|
||||||
|
// add administrators role if neither host or administrator is assigned
|
||||||
|
if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) &&
|
||||||
|
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)))
|
||||||
{
|
{
|
||||||
// add administrators role if host user role is not assigned
|
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Admin, null, true));
|
||||||
ids.Add(RoleNames.Admin);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
permission.Permissions = string.Join(";", ids.ToArray());
|
}
|
||||||
_permissions[i] = permission;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Oqtane.Modules.Controls
|
namespace Oqtane.Modules.Controls
|
||||||
{
|
{
|
||||||
public class RichTextEditorInterop
|
public class QuillEditorInterop
|
||||||
{
|
{
|
||||||
private readonly IJSRuntime _jsRuntime;
|
private readonly IJSRuntime _jsRuntime;
|
||||||
|
|
||||||
public RichTextEditorInterop(IJSRuntime jsRuntime)
|
public QuillEditorInterop(IJSRuntime jsRuntime)
|
||||||
{
|
{
|
||||||
_jsRuntime = jsRuntime;
|
_jsRuntime = jsRuntime;
|
||||||
}
|
}
|
||||||
@@ -105,13 +105,25 @@ namespace Oqtane.Modules.Controls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task InsertImage(ElementReference quillElement, string imageUrl, string altText)
|
public ValueTask<int> GetCurrentCursor(ElementReference quillElement)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _jsRuntime.InvokeAsync<int>("Oqtane.RichTextEditor.getCurrentCursor", quillElement);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new ValueTask<int>(Task.FromResult(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InsertImage(ElementReference quillElement, string imageUrl, string altText, int editorIndex)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_jsRuntime.InvokeAsync<object>(
|
_jsRuntime.InvokeAsync<object>(
|
||||||
"Oqtane.RichTextEditor.insertQuillImage",
|
"Oqtane.RichTextEditor.insertQuillImage",
|
||||||
quillElement, imageUrl, altText);
|
quillElement, imageUrl, altText, editorIndex);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
578
Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor
Normal file
578
Oqtane.Client/Modules/Controls/QuillJSTextEditor.razor
Normal file
@@ -0,0 +1,578 @@
|
|||||||
|
@namespace Oqtane.Modules.Controls
|
||||||
|
@inherits ModuleControlBase
|
||||||
|
@implements ITextEditor
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IStringLocalizer<QuillJSTextEditor> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<div class="quill-text-editor">
|
||||||
|
<TabStrip ActiveTab="@_activetab">
|
||||||
|
@if (_allowRichText)
|
||||||
|
{
|
||||||
|
<TabPanel Name="Rich" Heading="Rich Text Editor" ResourceKey="RichTextEditor">
|
||||||
|
@if (_richfilemanager)
|
||||||
|
{
|
||||||
|
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
|
||||||
|
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
<div class="d-flex justify-content-center mb-2">
|
||||||
|
@if (_allowFileManagement)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="InsertRichImage">@Localizer["InsertImage"]</button>
|
||||||
|
}
|
||||||
|
@if (_richfilemanager)
|
||||||
|
{
|
||||||
|
@((MarkupString)" ")
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="CloseRichFileManager">@Localizer["Close"]</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div @ref="@_toolBar">
|
||||||
|
@if (!string.IsNullOrEmpty(_toolbarContent))
|
||||||
|
{
|
||||||
|
@((MarkupString)_toolbarContent)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<select class="ql-header">
|
||||||
|
<option selected=""></option>
|
||||||
|
<option value="1"></option>
|
||||||
|
<option value="2"></option>
|
||||||
|
<option value="3"></option>
|
||||||
|
<option value="4"></option>
|
||||||
|
<option value="5"></option>
|
||||||
|
</select>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-bold"></button>
|
||||||
|
<button class="ql-italic"></button>
|
||||||
|
<button class="ql-underline"></button>
|
||||||
|
<button class="ql-strike"></button>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<select class="ql-color"></select>
|
||||||
|
<select class="ql-background"></select>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-list" value="ordered"></button>
|
||||||
|
<button class="ql-list" value="bullet"></button>
|
||||||
|
</span>
|
||||||
|
<span class="ql-formats">
|
||||||
|
<button class="ql-link"></button>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div @ref="@_editorElement"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
}
|
||||||
|
@if (_allowRawHtml)
|
||||||
|
{
|
||||||
|
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
|
||||||
|
@if (_rawfilemanager)
|
||||||
|
{
|
||||||
|
<FileManager @ref="_fileManager" Filter="@PageState.Site.ImageFiles" />
|
||||||
|
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
<div class="d-flex justify-content-center mb-2">
|
||||||
|
@if (_allowFileManagement)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="InsertRawImage">@Localizer["InsertImage"]</button>
|
||||||
|
}
|
||||||
|
@if (_rawfilemanager)
|
||||||
|
{
|
||||||
|
@((MarkupString)" ")
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="CloseRawFileManager">@Localizer["Close"]</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (ReadOnly)
|
||||||
|
{
|
||||||
|
<textarea id="@_rawhtmlid" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<textarea id="@_rawhtmlid" class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
|
||||||
|
}
|
||||||
|
</TabPanel>
|
||||||
|
}
|
||||||
|
@if (_allowSettings)
|
||||||
|
{
|
||||||
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||||
|
<div class="quill-text-editor-settings">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="Scope" ResourceKey="Scope" ResourceType="@resourceType" HelpText="Specify if settings are scoped to the module or site">Scope: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="Scope" class="form-select" value="@_scopeSetting" @onchange="(e => ScopeChanged(e))">
|
||||||
|
<option value="Module">@SharedLocalizer["Module"]</option>
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
<option value="Site">@SharedLocalizer["Site"]</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="AllowRichText" ResourceKey="AllowRichText" ResourceType="@resourceType" HelpText="Specify if editors can use the Rich Text Editor">Rich Text Editor? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="AllowRichText" class="form-select" @bind="@_allowRichTextSetting" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="AllowRawHtml" ResourceKey="AllowRawHtml" ResourceType="@resourceType" HelpText="Specify if editors can use the Raw HTML Editor">Raw HTML Editor? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="AllowRawHtml" class="form-select" @bind="@_allowRawHtmlSetting" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="AllowFileManagement" ResourceKey="AllowFileManagement" ResourceType="@resourceType" HelpText="Specify if editors can upload and insert images">Insert Images? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="AllowFileManagement" class="form-select" @bind="@_allowFileManagementSetting" required>
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="Theme" ResourceKey="Theme" ResourceType="@resourceType" HelpText="Specify the Rich Text Editor's theme">Theme: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" id="Theme" class="form-control" @bind="_themeSetting" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="DebugLevel" ResourceKey="DebugLevel" ResourceType="@resourceType" HelpText="Specify the Debug Level">Debug Level: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="DebugLevel" class="form-select" @bind="_debugLevelSetting">
|
||||||
|
@foreach (var level in _debugLevels)
|
||||||
|
{
|
||||||
|
<option value="@level">@level</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="ToolbarContent" ResourceKey="ToolbarContent" ResourceType="@resourceType" HelpText="Specify any toolbar content to customize the Rich Text Editor">Toolbar Content: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="ToolbarContent" class="form-control" @bind="_toolbarContentSetting" rows="3" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<div class="col-sm-9 offset-sm-3">
|
||||||
|
<button type="button" class="btn btn-success" @onclick="@(async () => await UpdateSettings())">@Localizer["SaveSettings"]</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabPanel>
|
||||||
|
}
|
||||||
|
</TabStrip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
public string Name => "QuillJS";
|
||||||
|
|
||||||
|
private string resourceType = "Oqtane.Modules.Controls.QuillJSTextEditor, Oqtane.Client";
|
||||||
|
|
||||||
|
private bool _settingsLoaded;
|
||||||
|
private bool _initialized = false;
|
||||||
|
|
||||||
|
private QuillEditorInterop _interop;
|
||||||
|
private FileManager _fileManager;
|
||||||
|
private string _activetab = "Rich";
|
||||||
|
private bool _allowSettings = false;
|
||||||
|
|
||||||
|
private bool _allowFileManagement = false;
|
||||||
|
private bool _allowRawHtml = false;
|
||||||
|
private bool _allowRichText = false;
|
||||||
|
private string _theme = "snow";
|
||||||
|
private string _debugLevel = "info";
|
||||||
|
private string _toolbarContent = string.Empty;
|
||||||
|
|
||||||
|
private string _scopeSetting = "Module";
|
||||||
|
private string _allowFileManagementSetting = "False";
|
||||||
|
private string _allowRawHtmlSetting = "False";
|
||||||
|
private string _allowRichTextSetting = "False";
|
||||||
|
private string _themeSetting = "snow";
|
||||||
|
private string _debugLevelSetting = "info";
|
||||||
|
private string _toolbarContentSetting = string.Empty;
|
||||||
|
|
||||||
|
private ElementReference _editorElement;
|
||||||
|
private ElementReference _toolBar;
|
||||||
|
private bool _richfilemanager = false;
|
||||||
|
private string _richhtml = string.Empty;
|
||||||
|
private string _originalrichhtml = string.Empty;
|
||||||
|
|
||||||
|
private bool _rawfilemanager = false;
|
||||||
|
private string _rawhtmlid = "RawHtmlEditor_" + Guid.NewGuid().ToString("N");
|
||||||
|
private string _rawhtml = string.Empty;
|
||||||
|
private string _originalrawhtml = string.Empty;
|
||||||
|
|
||||||
|
private string _message = string.Empty;
|
||||||
|
private bool _contentchanged = false;
|
||||||
|
private int _editorIndex;
|
||||||
|
|
||||||
|
private List<string> _debugLevels = new List<string> { "info", "log", "warn", "error" };
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool ReadOnly { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Placeholder { get; set; }
|
||||||
|
|
||||||
|
// the following parameters were supported by the original RichTextEditor and can be passed as optional static parameters
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool? AllowFileManagement { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool? AllowRichText { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool? AllowRawHtml { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Theme { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string DebugLevel { get; set; }
|
||||||
|
|
||||||
|
public override List<Resource> Resources { get; set; } = new List<Resource>()
|
||||||
|
{
|
||||||
|
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js", Location = ResourceLocation.Body },
|
||||||
|
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js", Location = ResourceLocation.Body },
|
||||||
|
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js", Location = ResourceLocation.Body }
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_interop = new QuillEditorInterop(JSRuntime);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(Placeholder))
|
||||||
|
{
|
||||||
|
Placeholder = Localizer["Placeholder"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnParametersSet()
|
||||||
|
{
|
||||||
|
LoadSettings();
|
||||||
|
|
||||||
|
if (!_allowRichText)
|
||||||
|
{
|
||||||
|
_activetab = "Raw";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
// include CSS theme
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
await interop.IncludeLink("", "stylesheet", $"{PageState?.Alias.BaseUrl}/css/quill/quill.{_theme}.css", "text/css", "", "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
await base.OnAfterRenderAsync(firstRender);
|
||||||
|
|
||||||
|
if (_allowRichText)
|
||||||
|
{
|
||||||
|
if (firstRender)
|
||||||
|
{
|
||||||
|
await _interop.CreateEditor(
|
||||||
|
_editorElement,
|
||||||
|
_toolBar,
|
||||||
|
ReadOnly,
|
||||||
|
Placeholder,
|
||||||
|
_theme,
|
||||||
|
_debugLevel);
|
||||||
|
|
||||||
|
await _interop.LoadEditorContent(_editorElement, _richhtml);
|
||||||
|
|
||||||
|
// preserve a copy of the content (Quill sanitizes content so we need to retrieve it from the editor as it may have been modified)
|
||||||
|
_originalrichhtml = await _interop.GetHtml(_editorElement);
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_initialized)
|
||||||
|
{
|
||||||
|
if (_contentchanged)
|
||||||
|
{
|
||||||
|
// reload editor if Content passed to component has changed
|
||||||
|
await _interop.LoadEditorContent(_editorElement, _richhtml);
|
||||||
|
_originalrichhtml = await _interop.GetHtml(_editorElement);
|
||||||
|
|
||||||
|
_contentchanged = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// preserve changed content on re-render event
|
||||||
|
var richhtml = await _interop.GetHtml(_editorElement);
|
||||||
|
if (richhtml != _richhtml)
|
||||||
|
{
|
||||||
|
_richhtml = richhtml;
|
||||||
|
await _interop.LoadEditorContent(_editorElement, _richhtml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(string content)
|
||||||
|
{
|
||||||
|
_richhtml = content;
|
||||||
|
_rawhtml = content;
|
||||||
|
_originalrichhtml = "";
|
||||||
|
_richhtml = content;
|
||||||
|
if (!_contentchanged)
|
||||||
|
{
|
||||||
|
_contentchanged = content != _originalrawhtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
_originalrawhtml = _rawhtml; // preserve for comparison later
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetContent()
|
||||||
|
{
|
||||||
|
// evaluate raw html content as first priority
|
||||||
|
if (_rawhtml != _originalrawhtml)
|
||||||
|
{
|
||||||
|
return _rawhtml;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var richhtml = "";
|
||||||
|
|
||||||
|
if (_allowRichText)
|
||||||
|
{
|
||||||
|
richhtml = await _interop.GetHtml(_editorElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml))
|
||||||
|
{
|
||||||
|
// convert Quill's empty content to empty string
|
||||||
|
if (richhtml == "<p><br></p>")
|
||||||
|
{
|
||||||
|
richhtml = string.Empty;
|
||||||
|
}
|
||||||
|
return richhtml;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// return original raw html content
|
||||||
|
return _originalrawhtml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseRichFileManager()
|
||||||
|
{
|
||||||
|
_richfilemanager = false;
|
||||||
|
_message = string.Empty;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseRawFileManager()
|
||||||
|
{
|
||||||
|
_rawfilemanager = false;
|
||||||
|
_message = string.Empty;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetHtml()
|
||||||
|
{
|
||||||
|
// evaluate raw html content as first priority
|
||||||
|
if (_rawhtml != _originalrawhtml)
|
||||||
|
{
|
||||||
|
return _rawhtml;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var richhtml = "";
|
||||||
|
|
||||||
|
if (_allowRichText)
|
||||||
|
{
|
||||||
|
richhtml = await _interop.GetHtml(_editorElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (richhtml != _originalrichhtml && !string.IsNullOrEmpty(richhtml))
|
||||||
|
{
|
||||||
|
// convert Quill's empty content to empty string
|
||||||
|
if (richhtml == "<p><br></p>")
|
||||||
|
{
|
||||||
|
richhtml = string.Empty;
|
||||||
|
}
|
||||||
|
return richhtml;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// return original raw html content
|
||||||
|
return _originalrawhtml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InsertRichImage()
|
||||||
|
{
|
||||||
|
_message = string.Empty;
|
||||||
|
if (_richfilemanager)
|
||||||
|
{
|
||||||
|
var file = _fileManager.GetFile();
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
await _interop.InsertImage(_editorElement, file.Url, ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name), _editorIndex);
|
||||||
|
_richhtml = await _interop.GetHtml(_editorElement);
|
||||||
|
_richfilemanager = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_message = Localizer["Message.Require.Image"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_editorIndex = await _interop.GetCurrentCursor(_editorElement);
|
||||||
|
_richfilemanager = true;
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InsertRawImage()
|
||||||
|
{
|
||||||
|
_message = string.Empty;
|
||||||
|
if (_rawfilemanager)
|
||||||
|
{
|
||||||
|
var file = _fileManager.GetFile();
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
int pos = await interop.GetCaretPosition(_rawhtmlid);
|
||||||
|
var image = "<img src=\"" + file.Url + "\" alt=\"" + ((!string.IsNullOrEmpty(file.Description)) ? file.Description : file.Name) + "\" class=\"img-fluid\">";
|
||||||
|
_rawhtml = _rawhtml.Substring(0, pos) + image + _rawhtml.Substring(pos);
|
||||||
|
_rawfilemanager = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_message = Localizer["Message.Require.Image"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_rawfilemanager = true;
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScopeChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_scopeSetting = (string)e.Value;
|
||||||
|
LoadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadSettings(bool reload = false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_settingsLoaded || reload)
|
||||||
|
{
|
||||||
|
_allowFileManagement = bool.Parse(GetSetting("Component", "QuillTextEditor_AllowFileManagement", "True"));
|
||||||
|
_allowRawHtml = bool.Parse(GetSetting("Component", "QuillTextEditor_AllowRawHtml", "True"));
|
||||||
|
_allowRichText = bool.Parse(GetSetting("Component", "QuillTextEditor_AllowRichText", "True"));
|
||||||
|
_theme = GetSetting("Component", "QuillTextEditor_Theme", "snow");
|
||||||
|
_debugLevel = GetSetting("Component", "QuillTextEditor_DebugLevel", "info");
|
||||||
|
_toolbarContent = GetSetting("Component", "QuillTextEditor_ToolbarContent", string.Empty);
|
||||||
|
|
||||||
|
// optional static parameter overrides
|
||||||
|
if (AllowFileManagement != null) _allowFileManagement = AllowFileManagement.Value;
|
||||||
|
if (AllowRichText != null) _allowRichText = AllowRichText.Value;
|
||||||
|
if (AllowRawHtml != null) _allowRawHtml = AllowRawHtml.Value;
|
||||||
|
if (!string.IsNullOrEmpty(Theme)) _theme = Theme;
|
||||||
|
if (!string.IsNullOrEmpty(DebugLevel)) _debugLevel = DebugLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
_allowSettings = PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList);
|
||||||
|
if (_allowSettings)
|
||||||
|
{
|
||||||
|
_allowFileManagementSetting = GetSetting(_scopeSetting, "QuillTextEditor_AllowFileManagement", "True");
|
||||||
|
_allowRawHtmlSetting = GetSetting(_scopeSetting, "QuillTextEditor_AllowRawHtml", "True");
|
||||||
|
_allowRichTextSetting = GetSetting(_scopeSetting, "QuillTextEditor_AllowRichText", "True");
|
||||||
|
_themeSetting = GetSetting(_scopeSetting, "QuillTextEditor_Theme", "snow");
|
||||||
|
_debugLevelSetting = GetSetting(_scopeSetting, "QuillTextEditor_DebugLevel", "info");
|
||||||
|
_toolbarContentSetting = GetSetting(_scopeSetting, "QuillTextEditor_ToolbarContent", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
_settingsLoaded = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSetting(string scope, string settingName, string defaultValue)
|
||||||
|
{
|
||||||
|
var settingValue = "";
|
||||||
|
switch (scope)
|
||||||
|
{
|
||||||
|
case "Component":
|
||||||
|
settingValue = SettingService.GetSetting(PageState.Site.Settings, settingName, defaultValue);
|
||||||
|
settingValue = SettingService.GetSetting(ModuleState.Settings, settingName, settingValue);
|
||||||
|
break;
|
||||||
|
case "Site":
|
||||||
|
settingValue = SettingService.GetSetting(PageState.Site.Settings, settingName, defaultValue);
|
||||||
|
break;
|
||||||
|
case "Module":
|
||||||
|
settingValue = SettingService.GetSetting(ModuleState.Settings, settingName, defaultValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return settingValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_scopeSetting == "Site" && UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowFileManagement", _allowFileManagementSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRawHtml", _allowRawHtmlSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRichText", _allowRichTextSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_Theme", _themeSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_DebugLevel", _debugLevelSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_ToolbarContent", _toolbarContentSetting);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
}
|
||||||
|
else if (_scopeSetting == "Module")
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowFileManagement", _allowFileManagementSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRawHtml", _allowRawHtmlSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_AllowRichText", _allowRichTextSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_Theme", _themeSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_DebugLevel", _debugLevelSetting);
|
||||||
|
settings = SettingService.SetSetting(settings, "QuillTextEditor_ToolbarContent", _toolbarContentSetting);
|
||||||
|
await SettingService.UpdateModuleSettingsAsync(settings,ModuleState.ModuleId);
|
||||||
|
}
|
||||||
|
LoadSettings(true);
|
||||||
|
|
||||||
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,226 +1,127 @@
|
|||||||
|
@using System.Text.RegularExpressions
|
||||||
|
@using Microsoft.AspNetCore.Components.Rendering
|
||||||
|
@using Microsoft.Extensions.DependencyInjection
|
||||||
@namespace Oqtane.Modules.Controls
|
@namespace Oqtane.Modules.Controls
|
||||||
@inherits ModuleControlBase
|
@inherits ModuleControlBase
|
||||||
|
@inject IServiceProvider ServiceProvider
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<RichTextEditor> Localizer
|
@inject IStringLocalizer<RichTextEditor> Localizer
|
||||||
|
|
||||||
<div class="row" style="margin-bottom: 50px;">
|
<div class="row" style="margin-bottom: 50px;">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<TabStrip>
|
@_textEditorComponent
|
||||||
<TabPanel Name="Rich" Heading="Rich Text Editor">
|
|
||||||
@if (AllowFileManagement)
|
|
||||||
{
|
|
||||||
@if (_filemanagervisible)
|
|
||||||
{
|
|
||||||
<FileManager @ref="_fileManager" Filter="@Constants.ImageFiles" />
|
|
||||||
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
|
|
||||||
<br />
|
|
||||||
}
|
|
||||||
<div class="d-flex justify-content-center mb-2">
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>
|
|
||||||
<button type="button" class="btn btn-primary" @onclick="InsertImage">@Localizer["InsertImage"]</button>
|
|
||||||
@if (_filemanagervisible)
|
|
||||||
{
|
|
||||||
@((MarkupString)" ")
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="CloseFileManager">@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>
|
|
||||||
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
|
|
||||||
<div class="d-flex justify-content-center mb-2">
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["SynchronizeContent"]</button>
|
|
||||||
</div>
|
|
||||||
@if (ReadOnly)
|
|
||||||
{
|
|
||||||
<textarea class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10" readonly></textarea>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<textarea class="form-control" placeholder="@Placeholder" @bind="@_rawhtml" rows="10"></textarea>
|
|
||||||
}
|
|
||||||
</TabPanel>
|
|
||||||
</TabStrip>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private ElementReference _editorElement;
|
private string _textEditorType;
|
||||||
private ElementReference _toolBar;
|
private RenderFragment _textEditorComponent;
|
||||||
private bool _filemanagervisible = false;
|
private ITextEditor _textEditor;
|
||||||
private FileManager _fileManager;
|
|
||||||
private string _richhtml = string.Empty;
|
|
||||||
private string _originalrichhtml = string.Empty;
|
|
||||||
private string _rawhtml = string.Empty;
|
|
||||||
private string _originalrawhtml = string.Empty;
|
|
||||||
private string _message = string.Empty;
|
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Content { get; set; }
|
public string Content { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool ReadOnly { get; set; } = false;
|
public bool ReadOnly { get; set; } = false;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Placeholder { get; set; } = "Enter Your Content...";
|
public string Placeholder { get; set; }
|
||||||
|
|
||||||
// parameters only applicable to rich text editor
|
[Parameter]
|
||||||
[Parameter]
|
public string Provider { get; set; }
|
||||||
public RenderFragment ToolbarContent { get; set; }
|
|
||||||
|
|
||||||
[Parameter]
|
[Parameter(CaptureUnmatchedValues = true)]
|
||||||
public string Theme { get; set; } = "snow";
|
public Dictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();
|
||||||
|
|
||||||
[Parameter]
|
protected override void OnInitialized()
|
||||||
public string DebugLevel { get; set; } = "info";
|
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public bool AllowFileManagement { get; set; } = true;
|
|
||||||
|
|
||||||
public override List<Resource> Resources => new List<Resource>()
|
|
||||||
{
|
{
|
||||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill.min.js" },
|
_textEditorType = GetTextEditorType();
|
||||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-blot-formatter.min.js" },
|
|
||||||
new Resource { ResourceType = ResourceType.Script, Bundle = "Quill", Url = "js/quill-interop.js" }
|
|
||||||
};
|
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
|
||||||
{
|
|
||||||
_richhtml = Content;
|
|
||||||
_rawhtml = Content;
|
|
||||||
_originalrawhtml = _rawhtml; // preserve for comparison later
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
await base.OnAfterRenderAsync(firstRender);
|
|
||||||
|
|
||||||
var interop = new RichTextEditorInterop(JSRuntime);
|
|
||||||
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
await interop.CreateEditor(
|
|
||||||
_editorElement,
|
|
||||||
_toolBar,
|
|
||||||
ReadOnly,
|
|
||||||
Placeholder,
|
|
||||||
Theme,
|
|
||||||
DebugLevel);
|
|
||||||
|
|
||||||
await interop.LoadEditorContent(_editorElement, _richhtml);
|
|
||||||
|
|
||||||
// preserve a copy of the rich text content (Quill sanitizes content so we need to retrieve it from the editor)
|
|
||||||
_originalrichhtml = await interop.GetHtml(_editorElement);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await interop.LoadEditorContent(_editorElement, _richhtml);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CloseFileManager()
|
|
||||||
{
|
|
||||||
_filemanagervisible = false;
|
|
||||||
_message = string.Empty;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RefreshRichText()
|
|
||||||
{
|
|
||||||
_richhtml = _rawhtml;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RefreshRawHtml()
|
|
||||||
{
|
|
||||||
var interop = new RichTextEditorInterop(JSRuntime);
|
|
||||||
_rawhtml = await interop.GetHtml(_editorElement);
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> GetHtml()
|
|
||||||
{
|
|
||||||
// evaluate raw html content as first priority
|
|
||||||
if (_rawhtml != _originalrawhtml)
|
|
||||||
{
|
|
||||||
return _rawhtml;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// return rich text content if it has changed
|
|
||||||
var interop = new RichTextEditorInterop(JSRuntime);
|
|
||||||
var richhtml = await interop.GetHtml(_editorElement);
|
|
||||||
if (richhtml != _originalrichhtml)
|
|
||||||
{
|
|
||||||
return richhtml;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// return original raw html content
|
|
||||||
return _originalrawhtml;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InsertImage()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
_message = string.Empty;
|
_textEditorComponent = (builder) =>
|
||||||
if (_filemanagervisible)
|
|
||||||
{
|
{
|
||||||
var file = _fileManager.GetFile();
|
CreateTextEditor(builder);
|
||||||
if (file != null)
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if(_textEditor != null)
|
||||||
|
{
|
||||||
|
_textEditor.Initialize(Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
await base.OnAfterRenderAsync(firstRender);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetHtml()
|
||||||
|
{
|
||||||
|
return await _textEditor.GetContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateTextEditor(RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
if(!string.IsNullOrEmpty(_textEditorType))
|
||||||
|
{
|
||||||
|
var editorType = Type.GetType(_textEditorType);
|
||||||
|
if (editorType != null)
|
||||||
{
|
{
|
||||||
var interop = new RichTextEditorInterop(JSRuntime);
|
builder.OpenComponent(0, editorType);
|
||||||
await interop.InsertImage(_editorElement, file.Url, file.Name);
|
|
||||||
_filemanagervisible = false;
|
var attributes = new Dictionary<string, object>
|
||||||
}
|
{
|
||||||
else
|
{ "Placeholder", Placeholder },
|
||||||
{
|
{ "ReadOnly", ReadOnly }
|
||||||
_message = Localizer["Message.Require.Image"];
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
|
||||||
|
private string GetTextEditorType()
|
||||||
|
{
|
||||||
|
const string EditorSettingName = "TextEditor";
|
||||||
|
|
||||||
|
if(!string.IsNullOrEmpty(Provider))
|
||||||
{
|
{
|
||||||
_filemanagervisible = true;
|
var provider = ServiceProvider.GetServices<ITextEditor>().FirstOrDefault(i => i.Name.Equals(Provider, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if(provider != null)
|
||||||
|
{
|
||||||
|
return Utilities.GetFullTypeName(provider.GetType().AssemblyQualifiedName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
StateHasChanged();
|
|
||||||
|
return SettingService.GetSetting(PageState.Site.Settings, EditorSettingName, Constants.DefaultTextEditor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,30 @@
|
|||||||
@namespace Oqtane.Modules.Controls
|
@namespace Oqtane.Modules.Controls
|
||||||
@inherits LocalizableComponent
|
@inherits LocalizableComponent
|
||||||
|
|
||||||
<div class="d-flex mt-2">
|
@if (IsVisible)
|
||||||
<div>
|
{
|
||||||
<a data-bs-toggle="collapse" class="app-link-unstyled" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name" @onclick:preventDefault="true">
|
<div class="d-flex mt-2">
|
||||||
<h5>@_heading</h5>
|
<div>
|
||||||
</a>
|
<a data-bs-toggle="collapse" class="app-link-unstyled" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name" @onclick:preventDefault="true">
|
||||||
|
<h5>@_heading</h5>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="ms-auto">
|
||||||
|
<a data-bs-toggle="collapse" class="app-link-unstyled float-right" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name" @onclick:preventDefault="true">
|
||||||
|
<i class="oi oi-chevron-bottom"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ms-auto">
|
<div class="d-flex">
|
||||||
<a data-bs-toggle="collapse" class="app-link-unstyled float-right" href="#@Name" aria-expanded="@_expanded" aria-controls="@Name" @onclick:preventDefault="true">
|
<hr class="app-rule" />
|
||||||
<i class="oi oi-chevron-bottom"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="collapse @_show" id="@Name">
|
||||||
<div class="d-flex">
|
@if (ChildContent != null)
|
||||||
<hr class="app-rule" />
|
{
|
||||||
</div>
|
@ChildContent
|
||||||
<div class="collapse @_show" id="@Name">
|
}
|
||||||
@ChildContent
|
</div>
|
||||||
</div>
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _heading = string.Empty;
|
private string _heading = string.Empty;
|
||||||
@@ -26,7 +32,7 @@
|
|||||||
private string _show = string.Empty;
|
private string _show = string.Empty;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public RenderFragment ChildContent { get; set; }
|
public RenderFragment ChildContent { get; set; } = null;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Name { get; set; } // required - the name of the section
|
public string Name { get; set; } // required - the name of the section
|
||||||
@@ -37,19 +43,15 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string Expanded { get; set; } // optional - will default to false if not provided
|
public string Expanded { get; set; } // optional - will default to false if not provided
|
||||||
|
|
||||||
protected override void OnInitialized()
|
[Parameter]
|
||||||
{
|
public bool IsVisible { get; set; } = true;
|
||||||
_heading = (!string.IsNullOrEmpty(Heading)) ? Heading : Name;
|
|
||||||
_expanded = (!string.IsNullOrEmpty(Expanded)) ? Expanded : "false";
|
|
||||||
if (_expanded == "true") { _show = "show"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
base.OnParametersSet();
|
base.OnParametersSet(); // must be included to call method in LocalizableComponent
|
||||||
|
|
||||||
_heading = !string.IsNullOrEmpty(Heading)
|
_heading = !string.IsNullOrEmpty(Heading) ? Localize(nameof(Heading), Heading) : Localize(nameof(Name), Name);
|
||||||
? Localize(nameof(Heading), Heading)
|
_expanded = (!string.IsNullOrEmpty(Expanded)) ? Expanded.ToLower() : "false";
|
||||||
: Localize(nameof(Name), Name);
|
if (_expanded == "true") { _show = "show"; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user