Compare commits
4505 Commits
v2.2.0
...
7a9941fe66
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a9941fe66 | |||
| b3b39f583a | |||
| 92c554e854 | |||
|
|
29fe3dfd0b | ||
|
|
985e50d415 | ||
|
|
11150b6a10 | ||
|
|
c499acdc4a | ||
|
|
b24e3252d9 | ||
|
|
e15787b1e4 | ||
|
|
d5f19d97e2 | ||
|
|
5543a4aeed | ||
|
|
9c333232e2 | ||
|
|
d52b95ea23 | ||
|
|
ef4fbcbb8a | ||
|
|
aa454b411f | ||
|
|
543e9339c7 | ||
|
|
7fff5c0d18 | ||
|
|
fa79f3f6fa | ||
|
|
c098839881 | ||
|
|
8d0d88c1b9 | ||
|
|
2b6ba0f410 | ||
|
|
11235009c0 | ||
|
|
4b05f7fdad | ||
|
|
338b0ae509 | ||
|
|
a437082952 | ||
|
|
ca9aba7b3b | ||
|
|
fe9f189734 | ||
|
|
018ac612f4 | ||
|
|
5bde40ec2b | ||
|
|
4d5780c192 | ||
|
|
ff6a810ad5 | ||
|
|
feec01ba00 | ||
|
|
1f05d12ef5 | ||
|
|
31aba14507 | ||
|
|
bbd6f13f36 | ||
|
|
68edbbbdb9 | ||
|
|
eb5a0dc1c9 | ||
|
|
7cea4f1792 | ||
|
|
c57c6abb1b | ||
|
|
a25b706c7b | ||
|
|
5d077e843d | ||
|
|
65bf3e9899 | ||
|
|
51ba3a01f5 | ||
|
|
f9ca611b8b | ||
|
|
a49b8728fd | ||
|
|
9234b91089 | ||
|
|
f3fcef52dd | ||
|
|
7f8b741981 | ||
|
|
f1791a709c | ||
|
|
30307fb05e | ||
|
|
57d443be8d | ||
|
|
84844c5043 | ||
|
|
9000f05961 | ||
|
|
6bef9b5c6e | ||
|
|
ffef1f4820 | ||
|
|
10c7bdbcaa | ||
|
|
e8f9888a41 | ||
|
|
8bac702be6 | ||
|
|
128bcecfe3 | ||
|
|
1390b3c489 | ||
|
|
a0f41341ac | ||
|
|
8ffa7ef7ff | ||
|
|
deb4607081 | ||
|
|
49c62f80e8 | ||
|
|
139793f3c0 | ||
|
|
f237cb9655 | ||
|
|
9f18c460d8 | ||
|
|
306a41b442 | ||
|
|
b53f54295d | ||
|
|
7e4f066694 | ||
|
|
90d72489d9 | ||
|
|
045c455324 | ||
|
|
60da903360 | ||
|
|
6d2a71f37e | ||
|
|
c8f60a12a4 | ||
|
|
11b2d3aa43 | ||
|
|
3d9c81d850 | ||
|
|
8ccb1a24f8 | ||
|
|
48c6796128 | ||
|
|
fc403f920b | ||
|
|
ca19496b5c | ||
|
|
0ae38f8a40 | ||
|
|
ad868ba841 | ||
|
|
f2aa39aa85 | ||
|
|
e76e0fc351 | ||
|
|
a348913888 | ||
|
|
5507006c53 | ||
|
|
994429f098 | ||
|
|
8efdcb9c49 | ||
|
|
db9a40db2b | ||
|
|
25667499e6 | ||
|
|
a728cd2d91 | ||
|
|
3f5f3ef10b | ||
|
|
0d708124c2 | ||
|
|
d31f73df14 | ||
|
|
3fed45438b | ||
|
|
8aa967fa1b | ||
|
|
6eaa3e342c | ||
|
|
6fc9e60f62 | ||
|
|
c39ffcf51c | ||
|
|
6f60a91f4c | ||
|
|
8031df6f28 | ||
|
|
da1e859fda | ||
|
|
8d4d25f1d1 | ||
|
|
6aff27778d | ||
|
|
ca2dcbfec0 | ||
|
|
24b666a382 | ||
|
|
9e34295529 | ||
|
|
10c55d056b | ||
|
|
753ab3bdd7 | ||
|
|
feee8def6f | ||
|
|
c208f12f8c | ||
|
|
dc926bf838 | ||
|
|
55a76b5204 | ||
|
|
53b837e763 | ||
|
|
3a551cdf25 | ||
|
|
c0c7f87dc9 | ||
|
|
ac77fd138b | ||
|
|
30d6e9d67c | ||
|
|
47db4334a1 | ||
|
|
fce97179e1 | ||
|
|
4f16cd2d01 | ||
|
|
fa587691b1 | ||
|
|
e0044658f9 | ||
|
|
53de1ddb36 | ||
|
|
da7b046092 | ||
|
|
d888d83a98 | ||
|
|
430f83e8e9 | ||
|
|
e7acd14faa | ||
|
|
1b00fa74bc | ||
|
|
4d572d8173 | ||
|
|
dbda85d8d9 | ||
|
|
95cb5dd66c | ||
|
|
f64e1c3a6a | ||
|
|
26a686c412 | ||
|
|
2505383f53 | ||
|
|
1a1e9ac6be | ||
|
|
7f20a3179e | ||
|
|
46431f0187 | ||
|
|
1ff8ec78c9 | ||
|
|
7840230c62 | ||
|
|
523db0a005 | ||
|
|
cc906d49ba | ||
|
|
fc23af89d3 | ||
|
|
5d8829ba63 | ||
|
|
31ccd80894 | ||
|
|
bac2234616 | ||
|
|
bd61db76a3 | ||
|
|
bc99e3b992 | ||
|
|
b7314b0813 | ||
|
|
4759bd569f | ||
|
|
b88c28f864 | ||
|
|
774ccb05f8 | ||
|
|
0ac48cba34 | ||
|
|
e36880fe3a | ||
|
|
713cf5de2c | ||
|
|
0fa336411f | ||
|
|
8ebdb09d68 | ||
|
|
40bc53001e | ||
|
|
b1656d1eea | ||
|
|
7aa54bf979 | ||
|
|
231f9bca84 | ||
|
|
e4f8596c19 | ||
|
|
020b7233d0 | ||
|
|
85fc0b3e2f | ||
|
|
5dcc7c14f3 | ||
|
|
7993d27b11 | ||
|
|
1f8c54ce74 | ||
|
|
73a414a34b | ||
|
|
8fa19c4a51 | ||
|
|
0667ae3e15 | ||
|
|
db1d00cd07 | ||
|
|
b27f092bef | ||
|
|
4eaea8e586 | ||
|
|
89cd7d3bbb | ||
|
|
2fff1d8d21 | ||
|
|
850631f00e | ||
|
|
1cea8846cf | ||
|
|
af48a48559 | ||
|
|
655c1762aa | ||
|
|
f706ccfd87 | ||
|
|
71e4c7f117 | ||
|
|
ad6182f4bd | ||
|
|
86bf0f65b0 | ||
|
|
7742f7747d | ||
|
|
eb998c41f2 | ||
|
|
657bd7c97c | ||
|
|
c8286148c1 | ||
|
|
e6ba2cce62 | ||
|
|
6105ff44b4 | ||
|
|
72da77be01 | ||
|
|
4c29b31f1b | ||
|
|
6e640108ed | ||
|
|
157322441d | ||
|
|
61d967e6af | ||
|
|
99f2158e55 | ||
|
|
1cba78cc4e | ||
|
|
1770c1ee11 | ||
|
|
a57fbea0cc | ||
|
|
f0c27c83f1 | ||
|
|
7873ca564c | ||
|
|
5ebc1fec24 | ||
|
|
f2559b7d4d | ||
|
|
1ee92a248e | ||
|
|
8376f98f21 | ||
|
|
810a3e0171 | ||
|
|
2eac9c3795 | ||
|
|
75f2425668 | ||
|
|
2dd1d7e926 | ||
|
|
5bb05a0a51 | ||
|
|
bc2c5b00c6 | ||
|
|
09f5e158dd | ||
|
|
4656471a0a | ||
|
|
69d58a4273 | ||
|
|
53a27677d4 | ||
|
|
f243ad0348 | ||
|
|
b4ce6bbb42 | ||
|
|
fa32937045 | ||
|
|
812e5f3c8e | ||
|
|
e2981e802c | ||
|
|
4ae4705c73 | ||
|
|
fbf4b12713 | ||
|
|
e4ece3e0dc | ||
|
|
9f231421be | ||
| 391827222e | |||
| c1721bd1a1 | |||
| f6630ae241 | |||
| 424cab64a8 | |||
|
|
b4fdbb5e48 | ||
|
|
fbf62ca30d | ||
|
|
05d2096fb8 | ||
|
|
7683af81bc | ||
|
|
bad10b3812 | ||
|
|
9f9522c2ed | ||
|
|
62879c3e52 | ||
|
|
45610f8dd7 | ||
|
|
18102cbd78 | ||
|
|
262d6a1529 | ||
|
|
1124ddaf90 | ||
|
|
981add3872 | ||
|
|
8d4b30140e | ||
|
|
b9c59137a8 | ||
|
|
0b1c7e06ca | ||
|
|
fcaf80cba6 | ||
|
|
6358b9eabb | ||
|
|
70a3fab1ff | ||
|
|
bdf86ace86 | ||
|
|
d57132d1e4 | ||
|
|
a6e87abf99 | ||
|
|
f1771610fe | ||
|
|
a88ea9780f | ||
|
|
bca0866d72 | ||
|
|
cebed93abf | ||
|
|
ee2b2e3569 | ||
|
|
cb8e9ee244 | ||
|
|
486184b16c | ||
|
|
9f9bd1988f | ||
|
|
ba1bfd1bc0 | ||
|
|
e12926e971 | ||
|
|
5b4db0de3b | ||
|
|
70ff55faa6 | ||
|
|
f2bd47d8bc | ||
|
|
e2b9c9e98e | ||
|
|
b0791a594f | ||
|
|
ea5eaa6ed2 | ||
|
|
ec3fd1d585 | ||
|
|
d76de22977 | ||
|
|
c0e3483cc7 | ||
|
|
0994cdf3b6 | ||
|
|
a76fd82262 | ||
|
|
2f919c7d69 | ||
|
|
f12592731b | ||
|
|
4a20e1a25d | ||
|
|
cc7111c3ff | ||
|
|
81972aed62 | ||
|
|
5e2092c6d4 | ||
|
|
d136f8ac91 | ||
|
|
5b23917940 | ||
|
|
2cda0a3798 | ||
|
|
f315ad1ce9 | ||
|
|
49f1c273c2 | ||
|
|
8518476c87 | ||
|
|
a34ed756db | ||
|
|
a48232c4e3 | ||
|
|
ac65e38390 | ||
|
|
eab3a753f5 | ||
|
|
9e5922e121 | ||
|
|
bf57b23776 | ||
|
|
8f4a20fd46 | ||
|
|
b3716da5ac | ||
|
|
2c129fd800 | ||
|
|
6fb18e7a25 | ||
|
|
38d28d6944 | ||
|
|
a187e1a7a2 | ||
|
|
7d7a19c7c2 | ||
|
|
6a2ae2153a | ||
|
|
f50ba1a91e | ||
|
|
b09575dbd6 | ||
|
|
c52ee3d91d | ||
|
|
1ced5c0425 | ||
|
|
e399a5c9b1 | ||
|
|
08dff5fb67 | ||
|
|
912760f2a7 | ||
|
|
4b62fdbf93 | ||
|
|
df593d43a7 | ||
|
|
89b1fba771 | ||
|
|
5505c91ae0 | ||
|
|
cc720ff399 | ||
|
|
29f07f6c56 | ||
|
|
a69e197a1f | ||
|
|
6dddd8eff8 | ||
|
|
51aada8922 | ||
|
|
b47bf40e8f | ||
|
|
48151bf365 | ||
|
|
659950996d | ||
|
|
6e656a4d0a | ||
|
|
bf308dd13d | ||
|
|
982f3b1943 | ||
|
|
7a4ea8cf1b | ||
|
|
7c0482a87c | ||
|
|
101ededd89 | ||
|
|
a8cbc0040e | ||
|
|
ed91bb445b | ||
|
|
f158a222f4 | ||
|
|
46bcad1fca | ||
|
|
5e147afb9f | ||
|
|
b061d4593f | ||
|
|
3fa520b4ef | ||
|
|
2df05b4afd | ||
|
|
e0569a6748 | ||
|
|
2e6ab398d9 | ||
|
|
94b03d2a6b | ||
|
|
f84fe30bb6 | ||
|
|
049ddef531 | ||
|
|
a1a214c742 | ||
|
|
c40a483ffa | ||
|
|
aff99acfae | ||
|
|
628129c08d | ||
|
|
679d34dfdf | ||
|
|
b2f65903ae | ||
|
|
2daefe0382 | ||
|
|
1214a11704 | ||
|
|
e55e0118c2 | ||
|
|
a1ac81e907 | ||
|
|
14ad68bf69 | ||
|
|
e3118c6e99 | ||
|
|
b41aeab8f8 | ||
|
|
1a738b358e | ||
|
|
f4b1e8035b | ||
|
|
324e985247 | ||
|
|
60faacd7d0 | ||
|
|
d4a4d7c346 | ||
|
|
189f8f1d27 | ||
|
|
ed353461da | ||
|
|
e9330d6c62 | ||
|
|
f53e7cc3f6 | ||
|
|
4f4258d532 | ||
|
|
c80910f355 | ||
|
|
12470ab178 | ||
|
|
704e091e9b | ||
|
|
f30f1e5c1f | ||
|
|
0741ce2197 | ||
|
|
fc81bae9b7 | ||
|
|
3fa68e4f96 | ||
|
|
05a767c7be | ||
|
|
8c1e8f6377 | ||
|
|
0fbae8d7da | ||
|
|
cec4b339f5 | ||
|
|
1a7656d8ee | ||
|
|
e173815810 | ||
|
|
620c768e05 | ||
|
|
7740679077 | ||
|
|
1ebc8ebff3 | ||
|
|
fa4fac70d5 | ||
|
|
8c83a18f93 | ||
|
|
a151ecfda0 | ||
|
|
dec0c0649c | ||
|
|
a356f893ac | ||
|
|
e2af4f74c3 | ||
|
|
99022b76e5 | ||
|
|
9dd6dc7523 | ||
|
|
6f588200d7 | ||
|
|
f3dbeae28e | ||
|
|
9f70361298 | ||
|
|
534353ce13 | ||
|
|
3f391a7354 | ||
|
|
0dd0752710 | ||
|
|
710fae4b0e | ||
|
|
de6c57a7ee | ||
|
|
7eee1fcd6a | ||
|
|
1fd2aedf96 | ||
|
|
ffdd7c063b | ||
|
|
a87af264eb | ||
|
|
3640cd2fdd | ||
|
|
f7cf25c4bb | ||
|
|
77dbd0d4c7 | ||
|
|
5a77c83e68 | ||
|
|
0a6763e08c | ||
|
|
b5aa206670 | ||
|
|
dbb4d9b64b | ||
|
|
6775edfd66 | ||
|
|
b06750ed65 | ||
|
|
57879c1891 | ||
|
|
65b55a76f2 | ||
|
|
8562a68306 | ||
|
|
160da46b5a | ||
|
|
ae5f70a739 | ||
|
|
4456e57466 | ||
|
|
24cd090c61 | ||
|
|
4f849f5d5f | ||
|
|
db2e86e84c | ||
|
|
14748ce2b3 | ||
|
|
527509732c | ||
|
|
aa9214477c | ||
|
|
db24ed8b55 | ||
|
|
349d1849d9 | ||
|
|
188be2fa8c | ||
|
|
5c4d7df734 | ||
|
|
37de18c670 | ||
|
|
0001e3844b | ||
|
|
65f171f701 | ||
|
|
c4308c239c | ||
|
|
2b6af3cb37 | ||
|
|
c5a16fbbc1 | ||
|
|
1db83f509b | ||
|
|
2a06304a2c | ||
|
|
7bbe684135 | ||
|
|
a996a88fc4 | ||
|
|
8cf9e7db51 | ||
|
|
ed981c67b7 | ||
|
|
6a77a0a5b9 | ||
|
|
bfb4b4431b | ||
|
|
3de44c0335 | ||
|
|
37afd1aec9 | ||
|
|
b2ac561673 | ||
|
|
c9bf7d9138 | ||
|
|
153a689bdb | ||
|
|
26b88f1a22 | ||
|
|
c66a5d028f | ||
|
|
8441c95a5c | ||
|
|
e0e32b0199 | ||
|
|
2bb76564e9 | ||
|
|
86ec25d4de | ||
|
|
ed9929963c | ||
|
|
36f50118ac | ||
|
|
72ddf27504 | ||
|
|
9bd36931ff | ||
|
|
056ef7a3d5 | ||
|
|
e483945d05 | ||
|
|
7a9c637e03 | ||
|
|
09ce543ea6 | ||
|
|
0ef24ebc3f | ||
|
|
a4419d3af6 | ||
|
|
935983c02a | ||
|
|
bd54ce5017 | ||
|
|
af6ed78b8e | ||
|
|
f72438996d | ||
|
|
9db2a55a5a | ||
|
|
950d90badb | ||
|
|
1864d180af | ||
|
|
0e82e98382 | ||
|
|
46023d35dc | ||
|
|
90d2e0a40b | ||
|
|
5f884e0796 | ||
|
|
16477052e2 | ||
|
|
66a05603f7 | ||
|
|
fe2a883386 | ||
|
|
ca7fdaa125 | ||
|
|
1283ec2008 | ||
|
|
d1f78f9048 | ||
|
|
45f43bfade | ||
|
|
4793ab4bc9 | ||
|
|
88b174dea8 | ||
|
|
06ca382bd7 | ||
|
|
b09175a8db | ||
|
|
677f68b08d | ||
|
|
8058b8dba4 | ||
|
|
4bc26f13c1 | ||
|
|
e6cf77e724 | ||
|
|
f8737c112e | ||
|
|
74b72ed9d4 | ||
|
|
4950391201 | ||
|
|
64a38d6e45 | ||
|
|
e2d8ee53f8 | ||
|
|
0204ff8dd5 | ||
|
|
4630ee6e93 | ||
|
|
c4f2abf143 | ||
|
|
e7444a0194 | ||
|
|
ffed7305ed | ||
|
|
334054bcd4 | ||
|
|
5bb98eb5b2 | ||
|
|
e842bd882a | ||
|
|
74bfb46f73 | ||
|
|
cd45bf4b70 | ||
|
|
51600bbcb0 | ||
|
|
8811a9bcaa | ||
|
|
4521f8a774 | ||
|
|
5b427783f8 | ||
|
|
9508983b15 | ||
|
|
fd09912cd7 | ||
|
|
01cc8584b6 | ||
|
|
c0b104e7c8 | ||
|
|
9a82021a82 | ||
|
|
1fb54a0b0f | ||
|
|
aa5ea61638 | ||
|
|
a59ec0258b | ||
|
|
b403f5cf71 | ||
|
|
0ac6a62b86 | ||
|
|
ed3743d3b6 | ||
|
|
3468cba000 | ||
|
|
5a4cdc5354 | ||
|
|
af4e19a57e | ||
|
|
26bb743679 | ||
|
|
96cc726e22 | ||
|
|
f4b00b01d0 | ||
|
|
127b2ca86d | ||
|
|
4b8b93e1b8 | ||
|
|
3aea412fe9 | ||
|
|
2aef96ad4f | ||
|
|
ec0a77230c | ||
|
|
b35e4bddd0 | ||
|
|
aa32beb341 | ||
|
|
efafe89b42 | ||
|
|
5ef2e49d9c | ||
|
|
1cfbf61a30 | ||
|
|
2bb5494b84 | ||
|
|
e8a41ccb47 | ||
|
|
7184f7f635 | ||
|
|
cc5727b7fa | ||
|
|
7f3d6ef6a5 | ||
|
|
44ce68097b | ||
|
|
d976cc6c19 | ||
|
|
d19d7d2a43 | ||
|
|
9bfaa02f97 | ||
|
|
2d9396b245 | ||
|
|
56e0da64ee | ||
|
|
997e9213f2 | ||
|
|
366569a23b | ||
|
|
36d5747b4f | ||
|
|
ea026c726c | ||
|
|
1e71e32c74 | ||
|
|
ed729bbd4f | ||
|
|
1a925221b7 | ||
|
|
af7b4db062 | ||
|
|
cfefe35e3f | ||
|
|
8d7845a44d | ||
|
|
3a15e6e5e9 | ||
|
|
3b8a51e855 | ||
|
|
f2cb34cc35 | ||
|
|
723ce62a34 | ||
|
|
2c9a2ea021 | ||
|
|
2be008d6d1 | ||
|
|
7fb51bdd0a | ||
|
|
abdbe3694f | ||
|
|
bd87e5012f | ||
|
|
55e18f2364 | ||
|
|
655e84072d | ||
|
|
ab5409d5b6 | ||
|
|
5a5da6486c | ||
|
|
7e99252429 | ||
|
|
10e0dcef8b | ||
|
|
80c8433aad | ||
|
|
5b0ae372f8 | ||
|
|
b5a1b529ab | ||
|
|
af821dcd9a | ||
|
|
10d3c81520 | ||
|
|
e3811b453a | ||
|
|
ca0fb05baa | ||
|
|
2b4b01bf6e | ||
|
|
3a1244bddc | ||
|
|
b8fd922b19 | ||
|
|
03f856025e | ||
|
|
45f04d24c3 | ||
|
|
2435d610c7 | ||
|
|
06572bcd14 | ||
|
|
3fab79afc0 | ||
|
|
2b7dd3fed5 | ||
|
|
d65efed032 | ||
|
|
c6896ea936 | ||
|
|
b7a41bddec | ||
|
|
6fd80c3737 | ||
|
|
0aa690b3b1 | ||
|
|
6d5bcfc6ed | ||
|
|
b60de69fa5 | ||
|
|
d991b57d08 | ||
|
|
1133d7fcba | ||
|
|
6fbf0383bb | ||
|
|
0296230219 | ||
|
|
dedfbba27a | ||
|
|
dc5441da07 | ||
|
|
585648b7f3 | ||
|
|
cd0ee1c26d | ||
|
|
d7a7be5af4 | ||
|
|
13e4267c11 | ||
|
|
15bc47e3e8 | ||
|
|
1a4380dcd7 | ||
|
|
5ace34b5cd | ||
|
|
f010c0f1fa | ||
|
|
2c721ad5dd | ||
|
|
8a7c2ce2c2 | ||
|
|
b2a7b813de | ||
|
|
e85cf04b99 | ||
|
|
ab6fa48172 | ||
|
|
c81905882f | ||
|
|
f0d31c1114 | ||
|
|
497b255216 | ||
|
|
d96286d771 | ||
|
|
2441647d75 | ||
|
|
77b780d631 | ||
|
|
cdd03bf3d4 | ||
|
|
e786c35f7d | ||
|
|
e83399acb1 | ||
|
|
ffea9e3210 | ||
|
|
f71a3a1ce3 | ||
|
|
a5ccc23604 | ||
|
|
1ed4c8a094 | ||
|
|
4a74549c1b | ||
|
|
a499cfb98f | ||
|
|
01038c8296 | ||
|
|
7407f79b3d | ||
|
|
a845dd1976 | ||
|
|
9d7549da70 | ||
|
|
f5cc61384f | ||
|
|
844778d36a | ||
|
|
871b0a274e | ||
|
|
737740a3ca | ||
|
|
ae8d600600 | ||
|
|
2f1691bfb0 | ||
|
|
a3d25f91c8 | ||
|
|
ff84b50817 | ||
|
|
0be8242284 | ||
|
|
e25a6259ea | ||
|
|
1578f82efb | ||
|
|
b5f75f0c5e | ||
|
|
601caab3b6 | ||
|
|
6d3092f440 | ||
|
|
ef27937c7a | ||
|
|
f4a7b79c4f | ||
|
|
2531776a48 | ||
|
|
ced80419aa | ||
|
|
ad2816f4e8 | ||
|
|
3528b8c674 | ||
|
|
80c83c626d | ||
|
|
6a355f2aea | ||
|
|
7d94e4a53a | ||
|
|
823c04742e | ||
|
|
043fb1abd1 | ||
|
|
f01e85b690 | ||
|
|
7eb1298847 | ||
|
|
a5480c9a96 | ||
|
|
f948600e86 | ||
|
|
420182b9bf | ||
|
|
8c430ce1a6 | ||
|
|
d3717dbe19 | ||
|
|
caa83d769f | ||
|
|
365f87828f | ||
|
|
f780887866 | ||
|
|
43627d4bb8 | ||
|
|
5d7b276cd1 | ||
|
|
23597eb997 | ||
|
|
b6948367f8 | ||
|
|
db6dd5abee | ||
|
|
702eb9e466 | ||
|
|
3c99006226 | ||
|
|
aaaf5683a5 | ||
|
|
92aa2236c0 | ||
|
|
d2592f72d6 | ||
|
|
27120d6cc9 | ||
|
|
a2669d35c3 | ||
|
|
574164081b | ||
|
|
00c2f8dcd8 | ||
|
|
0202bf60e5 | ||
|
|
16436a171b | ||
|
|
4cf4e0eabd | ||
|
|
5edb98dfb4 | ||
|
|
899bf22e15 | ||
|
|
9a33167a6c | ||
|
|
2dc068aa21 | ||
|
|
0f2aa4d2e1 | ||
|
|
a699f5c7bc | ||
|
|
ab807de3c0 | ||
|
|
68514bcb36 | ||
|
|
422bf8da59 | ||
|
|
de92dc93dd | ||
|
|
5a91b143b6 | ||
|
|
c745e85706 | ||
|
|
0f698e0c50 | ||
|
|
cd16d77bf0 | ||
|
|
fdbdd0ef4c | ||
|
|
71485f4a82 | ||
|
|
013056a6e5 | ||
|
|
263498fbdc | ||
|
|
f46ac2c007 | ||
|
|
c23841b6db | ||
|
|
18b1b5fca5 | ||
|
|
6707ac2697 | ||
|
|
d85a2fc8ce | ||
|
|
a8997e8f17 | ||
|
|
c8a22d9537 | ||
|
|
910669f786 | ||
|
|
89312c6796 | ||
|
|
a6aa96fdb0 | ||
|
|
7deb0b06af | ||
|
|
784f3771b3 | ||
|
|
80316824f7 | ||
|
|
cbaebb7b7c | ||
|
|
97d3764b6e | ||
|
|
002f8117cb | ||
|
|
0dfdb12431 | ||
|
|
d77e898929 | ||
|
|
4c937be884 | ||
|
|
c25cce4398 | ||
|
|
58c422285a | ||
|
|
f2f22f35e8 | ||
|
|
15867a7807 | ||
|
|
22e3161a9b | ||
|
|
b0c8203b24 | ||
|
|
e2c404d8bb | ||
|
|
5ee1731c92 | ||
|
|
06e8d3b660 | ||
|
|
f09709aedb | ||
|
|
598d5decac | ||
|
|
7832a6053e | ||
|
|
588748230e | ||
|
|
8668165c72 | ||
|
|
a967332f89 | ||
|
|
6719d242bd | ||
|
|
3565185808 | ||
|
|
ce51262197 | ||
|
|
992a786c2b | ||
|
|
038df95aa0 | ||
|
|
4ebd660de2 | ||
|
|
a9ea41a488 | ||
|
|
196594b490 | ||
|
|
1516d5af10 | ||
|
|
ffcd1595a9 | ||
|
|
42e5c6e111 | ||
|
|
8a9651dc50 | ||
|
|
4be2f4f2d9 | ||
|
|
369bf7a235 | ||
|
|
136545b404 | ||
|
|
73ea17ae0f | ||
|
|
23010acef4 | ||
|
|
4f74962ce2 | ||
|
|
7f978c7845 | ||
|
|
731fd46ea2 | ||
|
|
859759d691 | ||
|
|
5e9567158f | ||
|
|
51d244f3aa | ||
|
|
4c5a07edd5 | ||
|
|
8113a754a1 | ||
|
|
3cd40c6195 | ||
|
|
56cfb2ce06 | ||
|
|
72087823ac | ||
|
|
bcf7866fe2 | ||
|
|
b64772e484 | ||
|
|
088d665942 | ||
|
|
7d6c10befb | ||
|
|
f88e3d04b8 | ||
|
|
f57676a22b | ||
|
|
8618cb62e4 | ||
|
|
c31c88ed1f | ||
|
|
6022acd21f | ||
|
|
b3071b9272 | ||
|
|
ec6a6d6653 | ||
|
|
52f552b4de | ||
|
|
2643d3396b | ||
|
|
62d59a09cf | ||
|
|
a68ff8a4f0 | ||
|
|
93d4bfcd7a | ||
|
|
5fb80c1a7b | ||
|
|
04b38444ce | ||
|
|
ca3df02002 | ||
|
|
d952c33fab | ||
|
|
93bc1cd5af | ||
|
|
0e5b370ee8 | ||
|
|
f4fd4e28c9 | ||
|
|
ec8433eb45 | ||
|
|
0d4a40e9bb | ||
|
|
c3668f4179 | ||
|
|
3adb7ecb1c | ||
|
|
aa5b84a214 | ||
|
|
3d83fccbf1 | ||
|
|
4c4255be6b | ||
|
|
ed6054b082 | ||
|
|
f60f7a4dc1 | ||
|
|
998dc95cb2 | ||
|
|
12f5d7b846 | ||
|
|
906ae0a43e | ||
|
|
485b774876 | ||
|
|
3121cf5b75 | ||
|
|
ce7570dae2 | ||
|
|
b5ea0dfbc7 | ||
|
|
dd0f8f4772 | ||
|
|
9d0ab34274 | ||
|
|
e6038be6f7 | ||
|
|
bd2153a0ed | ||
|
|
e526deac20 | ||
|
|
b65f165dcf | ||
|
|
f9fbe5adc2 | ||
|
|
d1e73571a1 | ||
|
|
1047058676 | ||
|
|
29a1e77da8 | ||
|
|
290547e482 | ||
|
|
3df45ca20f | ||
|
|
cc06258484 | ||
|
|
2c262d0655 | ||
|
|
1875e1e158 | ||
|
|
1c95967b31 | ||
|
|
2ae98929de | ||
|
|
6a1dd38cbb | ||
|
|
c458a77d27 | ||
|
|
352c23f389 | ||
|
|
6c5a1dc2e1 | ||
|
|
004ff1e91d | ||
|
|
f7de4c567b | ||
|
|
b2ad1010ac | ||
|
|
660e164ff8 | ||
|
|
eb6dc80b50 | ||
|
|
a9882cc96a | ||
|
|
fb5a2ce178 | ||
|
|
d441b31dc7 | ||
|
|
d4239fe7e0 | ||
|
|
6a98999105 | ||
|
|
5caa1fe7d4 | ||
|
|
ef33bdb65e | ||
|
|
14a463382b | ||
|
|
1ad79874c8 | ||
|
|
1a61a58d28 | ||
|
|
3e3c973679 | ||
|
|
9dede84d20 | ||
|
|
e78b11cf62 | ||
|
|
341ca5a330 | ||
|
|
e5d8c02def | ||
|
|
947aa08c42 | ||
|
|
9a04d436bd | ||
|
|
be0754f568 | ||
|
|
93c4bbc0d1 | ||
|
|
b98535810b | ||
|
|
393cf8da1f | ||
|
|
ea2846973a | ||
|
|
3398c1cbfe | ||
|
|
39c79ea68e | ||
|
|
66900f4a32 | ||
|
|
df71dd14f7 | ||
|
|
8113ca3069 | ||
|
|
d468e675c2 | ||
|
|
1e84cedf82 | ||
|
|
7f4087e3de | ||
|
|
facd3c8956 | ||
|
|
3e50deecb7 | ||
|
|
628c504f84 | ||
|
|
e1ada78c1f | ||
|
|
09fa1e365c | ||
|
|
28b6b03d06 | ||
|
|
a4395b62ff | ||
|
|
4511acf273 | ||
|
|
fde53a2d83 | ||
|
|
f6cd04fdb8 | ||
|
|
a5eede6c7a | ||
|
|
2e83817c83 | ||
|
|
82aea40ae4 | ||
|
|
1b289eae24 | ||
|
|
81420b2c88 | ||
|
|
775731b745 | ||
|
|
489e7d4a67 | ||
|
|
b6508764d8 | ||
|
|
6dedd87305 | ||
|
|
89fa29b310 | ||
|
|
be5df9c22a | ||
|
|
db17739716 | ||
|
|
1439362a5e | ||
|
|
65c1b04772 | ||
|
|
83d30ebdc4 | ||
|
|
b7928a5255 | ||
|
|
a83ff9253d | ||
|
|
8cdcdaf6d9 | ||
|
|
d208edf153 | ||
|
|
8ada4765b1 | ||
|
|
9d3a808c24 | ||
|
|
828eb80266 | ||
|
|
0639f6c1d1 | ||
|
|
49b971280f | ||
|
|
3682a1010d | ||
|
|
f8b58866dc | ||
|
|
5d8d815d84 | ||
|
|
67743c7597 | ||
|
|
b33cc90447 | ||
|
|
78177f7890 | ||
|
|
3c417bfa99 | ||
|
|
f2c8d80ff8 | ||
|
|
8c1eb1e19f | ||
|
|
eb7188e81b | ||
|
|
f352fc5c67 | ||
|
|
ac313722f9 | ||
|
|
64c7f1962c | ||
|
|
c83f994b21 | ||
|
|
10f38a72f7 | ||
|
|
da35434f58 | ||
|
|
c79409e094 | ||
|
|
355ce00968 | ||
|
|
56c832f3ba | ||
|
|
40abb2720e | ||
|
|
9b6051afee | ||
|
|
dcf6f26792 | ||
|
|
85734c1146 | ||
|
|
c3fb8fcb6e | ||
|
|
013bbc1638 | ||
|
|
b0669a3b60 | ||
|
|
6f5da1ce7c | ||
|
|
3351732a2f | ||
|
|
d4d4034ecd | ||
|
|
b45e2742c3 | ||
|
|
42eba290e4 | ||
|
|
32d1e08b57 | ||
|
|
7b7d19da7c | ||
|
|
f78e400918 | ||
|
|
be4e9bf7e9 | ||
|
|
48f2079f88 | ||
|
|
36ad1ceef2 | ||
|
|
1f2e2148d5 | ||
|
|
76f3d345f9 | ||
|
|
f2c854b53a | ||
|
|
e2d336d90b | ||
|
|
c74065ff26 | ||
|
|
b6d97dc5d5 | ||
|
|
1c1c26948a | ||
|
|
d954e3ffb7 | ||
|
|
d9759a95eb | ||
|
|
d196402dd0 | ||
|
|
0f2b5531de | ||
|
|
26e4398905 | ||
|
|
06995f22fe | ||
|
|
caa2073d48 | ||
|
|
d9466fe4bc | ||
|
|
72e623a3a7 | ||
|
|
69bc06685f | ||
|
|
bf175984f3 | ||
|
|
4d4a7bfd0d | ||
|
|
044cee30a5 | ||
|
|
3c0c5aed60 | ||
|
|
e194971727 | ||
|
|
bbe85def23 | ||
|
|
9c2d53f2ae | ||
|
|
1cf36e2156 | ||
|
|
ce96e309af | ||
|
|
9ea7dc8b3c | ||
|
|
282242efd2 | ||
|
|
351ba22d64 | ||
|
|
2916625747 | ||
|
|
744dbeb7a3 | ||
|
|
fbb3c309ee | ||
|
|
473c265bac | ||
|
|
01b06c2c3c | ||
|
|
8b1f95c743 | ||
|
|
ff5dbec579 | ||
|
|
9620c5a98f | ||
|
|
7e849a2e95 | ||
|
|
229aed306e | ||
|
|
d718969cbd | ||
|
|
8593f4c3a9 | ||
|
|
d4f71d5026 | ||
|
|
a683a5f206 | ||
|
|
7917cc3eb5 | ||
|
|
6cd7ca755e | ||
|
|
fb7dfdc800 | ||
|
|
e096af320f | ||
|
|
65bff8f511 | ||
|
|
ca19d8a842 | ||
|
|
cde08c64ce | ||
|
|
3d6d7b7cb9 | ||
|
|
a56ef6f398 | ||
|
|
5a38b6614d | ||
|
|
3d5c44e8aa | ||
|
|
46a68bd5e7 | ||
|
|
c77ded51a9 | ||
|
|
59bd9fdc22 | ||
|
|
b81941394c | ||
|
|
197d5ca1f2 | ||
|
|
97eb986fa6 | ||
|
|
aba3110e31 | ||
|
|
51c27be236 | ||
|
|
592255284f | ||
|
|
82fd41dd4c | ||
|
|
4ae8df9652 | ||
|
|
759b19e444 | ||
|
|
36f705e46f | ||
|
|
1f0347682e | ||
|
|
1aee385679 | ||
|
|
5dd8191692 | ||
|
|
b6422f9b80 | ||
|
|
a6c2c9c92f | ||
|
|
247c573a6e | ||
|
|
aa435d6e94 | ||
|
|
f6858c221b | ||
|
|
437aa4510b | ||
|
|
430572fb32 | ||
|
|
9f0b755d6f | ||
|
|
66acb55a57 | ||
|
|
f936d4c36e | ||
|
|
c3ddb8df56 | ||
|
|
81ce920e69 | ||
|
|
3cb875d139 | ||
|
|
fdb217d5c6 | ||
|
|
085cae3b5f | ||
|
|
e2f99a1554 | ||
|
|
aee0c27da7 | ||
|
|
accbf4ad8b | ||
|
|
0ac1901f6b | ||
|
|
c0a0deea78 | ||
|
|
e3f099441c | ||
|
|
840dd25cd1 | ||
|
|
a493969f9b | ||
|
|
cca42a10a1 | ||
|
|
2f4aa98c3c | ||
|
|
175cb9588c | ||
|
|
a8976e7559 | ||
|
|
b663528fb0 | ||
|
|
1a2ad55677 | ||
|
|
513d2a88c0 | ||
|
|
57ef4c0396 | ||
|
|
e9599ca2f4 | ||
|
|
2fc6dbc222 | ||
|
|
225933c442 | ||
|
|
36e2f048d7 | ||
|
|
0e158bce59 | ||
|
|
202201fd31 | ||
|
|
4f8c928f44 | ||
|
|
ada062cf00 | ||
|
|
32dc12912a | ||
|
|
671d21adf4 | ||
|
|
4e3f8e4b67 | ||
|
|
586bb62073 | ||
|
|
151bf83ab1 | ||
|
|
8c618edb5a | ||
|
|
6d6b0cf8c9 | ||
|
|
c610608890 | ||
|
|
28da61daab | ||
|
|
cbfc90f60b | ||
|
|
fec92959d4 | ||
|
|
bb00d81eba | ||
|
|
bb55644c06 | ||
|
|
16215847cd | ||
|
|
8ee9aed817 | ||
|
|
515c6402b9 | ||
|
|
d1b94ec203 | ||
|
|
a037d9167e | ||
|
|
3054d33e62 | ||
|
|
6651e641e1 | ||
|
|
35f873a342 | ||
|
|
2d03ff38a1 | ||
|
|
44a3db417b | ||
|
|
f9ca702a12 | ||
|
|
4073ff38eb | ||
|
|
f0e2c9f1b6 | ||
|
|
cf040f51b5 | ||
|
|
dcf919fb36 | ||
|
|
aa19b81a68 | ||
|
|
db8d77365c | ||
|
|
280eaea84a | ||
|
|
340ef46469 | ||
|
|
a5f8651941 | ||
|
|
8a18ee548e | ||
|
|
ef791aa22a | ||
|
|
4bdf2e1cc0 | ||
|
|
ffa0ca9379 | ||
|
|
d3b3d46fc1 | ||
|
|
7e7dd8efa9 | ||
|
|
b4506f1133 | ||
|
|
7350c79113 | ||
|
|
5f7b60d3f4 | ||
|
|
266495a611 | ||
|
|
f5b4a7e77b | ||
|
|
51ed0f6487 | ||
|
|
bd70def18a | ||
|
|
93a9cf3b31 | ||
|
|
751287999f | ||
|
|
d433850cbf | ||
|
|
c93e70e2dc | ||
|
|
a129dd989a | ||
|
|
40999c3ff4 | ||
|
|
18a01d672c | ||
|
|
3648f99920 | ||
|
|
e823412f56 | ||
|
|
d090f446c9 | ||
|
|
19985d1742 | ||
|
|
ab52251116 | ||
|
|
acb6c0187c | ||
|
|
90f9c24720 | ||
|
|
415bec4646 | ||
|
|
5559f20511 | ||
|
|
6ea3399829 | ||
|
|
9e3df97737 | ||
|
|
0ac40a4d77 | ||
|
|
24bf5e8102 | ||
|
|
56eebb03c7 | ||
|
|
1cd4d6d0df | ||
|
|
22d4a8232a | ||
|
|
48076c25bf | ||
|
|
478a308e73 | ||
|
|
8ca2f0a49f | ||
|
|
4a35d7364b | ||
|
|
8b2e55a969 | ||
|
|
9b14b70687 | ||
|
|
1c01087eda | ||
|
|
5137e5a301 | ||
|
|
0fea8365b8 | ||
|
|
116a615b84 | ||
|
|
ef272dd6a8 | ||
|
|
4462ae9cae | ||
|
|
66ffad0b4e | ||
|
|
81e0fc940c | ||
|
|
3e8794db1b | ||
|
|
617622d4d8 | ||
|
|
a4240f972b | ||
|
|
42feff8882 | ||
|
|
70edd9686f | ||
|
|
d298cb2e1c | ||
|
|
3fef3f2dc3 | ||
|
|
c85f9d6ae4 | ||
|
|
85e7ac7cd7 | ||
|
|
5c7db61a7e | ||
|
|
497f9ca0b1 | ||
|
|
0c50f7a322 | ||
|
|
740bcbd12c | ||
|
|
c92be4f270 | ||
|
|
e2a7271ab2 | ||
|
|
64766713fa | ||
|
|
59bba83b1d | ||
|
|
8ac1217165 | ||
|
|
5443629ec5 | ||
|
|
8f9b41cb62 | ||
|
|
7df5eba775 | ||
|
|
e29c6ac593 | ||
|
|
4f3190bf73 | ||
|
|
f0878fccb5 | ||
|
|
b0e121a53f | ||
|
|
eda48ab0e6 | ||
|
|
6a1014d8c1 | ||
|
|
e0f87315bc | ||
|
|
f2c5dca5e7 | ||
|
|
3c435a804f | ||
|
|
7ee6775251 | ||
|
|
98adc2ecc1 | ||
|
|
45afbbdac6 | ||
|
|
a18260747b | ||
|
|
0c80e28754 | ||
|
|
719bc374ac | ||
|
|
d7a290c595 | ||
|
|
4b2bd33baa | ||
|
|
efbe4d697c | ||
|
|
a8662bdb8b | ||
|
|
d822225465 | ||
|
|
c6373ef582 | ||
|
|
5a2af6d0f9 | ||
|
|
52000d6a41 | ||
|
|
befa13eaf2 | ||
|
|
4ac68a81a3 | ||
|
|
71e472f330 | ||
|
|
ada8809ec0 | ||
|
|
25ea518266 | ||
|
|
f3720c3b94 | ||
|
|
b942a84b15 | ||
|
|
574ca90229 | ||
|
|
5610a14e49 | ||
|
|
c5bc62e6df | ||
|
|
e9f6a85cad | ||
|
|
8921011b27 | ||
|
|
90ef3f6c94 | ||
|
|
e84c75f4a8 | ||
|
|
68e20cd860 | ||
|
|
76bdcea4b1 | ||
|
|
f758cb7e6e | ||
|
|
1108477810 | ||
|
|
deb6a9e51c | ||
|
|
9660f20b87 | ||
|
|
4d26468ede | ||
|
|
125a0979d5 | ||
|
|
98bdfd3dbe | ||
|
|
ea72880e74 | ||
|
|
17fec7d6e1 | ||
|
|
d9de64604e | ||
|
|
6275ab23ff | ||
|
|
8c0271643d | ||
|
|
c3f041dc87 | ||
|
|
80e5e84341 | ||
|
|
938eee80a9 | ||
|
|
7abc2289de | ||
|
|
bb79b9ed74 | ||
|
|
1e89a8625c | ||
|
|
90b0f04b3c | ||
|
|
0f019cd9b6 | ||
|
|
1209739398 | ||
|
|
b99db2b353 | ||
|
|
f057688e7d | ||
|
|
12ae2d0c76 | ||
|
|
9d91d5a127 | ||
|
|
6015f0887a | ||
|
|
d4c473d7b3 | ||
|
|
2f3978deed | ||
|
|
34af53a15b | ||
|
|
a4eb3d7a0b | ||
|
|
5b46dd7293 | ||
|
|
4b17847ea5 | ||
|
|
acbe000f97 | ||
|
|
599071b68b | ||
|
|
2bacee919d | ||
|
|
50d35e4196 | ||
|
|
e321998b85 | ||
|
|
340c02b2af | ||
|
|
69a295fe57 | ||
|
|
64830aae9f | ||
|
|
8969b1273f | ||
|
|
475faf7943 | ||
|
|
45b1d405a6 | ||
|
|
f60c8078e4 | ||
|
|
6701e49f9a | ||
|
|
0efb3c3284 | ||
|
|
aaf3cdfdac | ||
|
|
e00c261777 | ||
|
|
1eafed755d | ||
|
|
7f6a08ae50 | ||
|
|
9901816fb9 | ||
|
|
3cbe6c1e95 | ||
|
|
679c99274e | ||
|
|
b6fa0f1ff6 | ||
|
|
9ff64b95e1 | ||
|
|
3a9885abd8 | ||
|
|
503210942c | ||
|
|
0178e015e3 | ||
|
|
7604992c35 | ||
|
|
ee9e551788 | ||
|
|
22063248ca | ||
|
|
e4ce18b35c | ||
|
|
532890674e | ||
|
|
791a3b67e6 | ||
|
|
84b560ef29 | ||
|
|
03f081f3f4 | ||
|
|
08213ae86e | ||
|
|
73abc511a8 | ||
|
|
1c943cc259 | ||
|
|
af62a89a6b | ||
|
|
af7ca5b897 | ||
|
|
27356ef747 | ||
|
|
b27f80ef87 | ||
|
|
b4aa73fc64 | ||
|
|
bbf444572b | ||
|
|
b3706574de | ||
|
|
83062f8bfb | ||
|
|
1d7fcfdaa1 | ||
|
|
58ab12b4cb | ||
|
|
28c649629f | ||
|
|
5ec190225d | ||
|
|
0e0d404997 | ||
|
|
3f16b908ca | ||
|
|
54549b261c | ||
|
|
8ce07ced9e | ||
|
|
37a5144e8f | ||
|
|
59fed7dda8 | ||
|
|
0a85a2868c | ||
|
|
0d493b3250 | ||
|
|
74530d4b1e | ||
|
|
7548c52e21 | ||
|
|
fa49844c64 | ||
|
|
3508ae1e0a | ||
|
|
f013ee64a2 | ||
|
|
af3da7ca6e | ||
|
|
cb728f65b3 | ||
|
|
af6af190cc | ||
|
|
cbcc8455ca | ||
|
|
af35fb79fe | ||
|
|
0515aaa946 | ||
|
|
1d00330e7a | ||
|
|
3fa6dcea16 | ||
|
|
51425cac4a | ||
|
|
7ce61a5d2b | ||
|
|
5e5caa979b | ||
|
|
a719518f8f | ||
|
|
d0e5aef443 | ||
|
|
b82a811c33 | ||
|
|
3356dcf8f7 | ||
|
|
8f1cc26537 | ||
|
|
27dafa83ac | ||
|
|
c1be1f329f | ||
|
|
c757cef549 | ||
|
|
da9e4c026c | ||
|
|
5821d67e69 | ||
|
|
ed14f6d13f | ||
|
|
35bdd8b4ef | ||
|
|
9cf2d30e77 | ||
|
|
a2140a3b7b | ||
|
|
900d026bcb | ||
|
|
68604ec15a | ||
|
|
f7c0ebb8d3 | ||
|
|
8be5d0c72d | ||
|
|
15c8b724e6 | ||
|
|
d6949200f9 | ||
|
|
1c2abe794a | ||
|
|
0bcf393586 | ||
|
|
bc0573918f | ||
|
|
175675ad99 | ||
|
|
b00c2afc46 | ||
|
|
c125a7fe07 | ||
|
|
a42ab32436 | ||
|
|
797a64976e | ||
|
|
ac377a8b68 | ||
|
|
842b7b1402 | ||
|
|
d449396ad5 | ||
|
|
532a87d064 | ||
|
|
e1cdc7b387 | ||
|
|
d9d917e267 | ||
|
|
8048788042 | ||
|
|
790fc88e47 | ||
|
|
7f970d489f | ||
|
|
9d85ca07f4 | ||
|
|
d75e3acdf3 | ||
|
|
694cda0e99 | ||
|
|
94f134c6a7 | ||
|
|
83f329d93c | ||
|
|
de49387fca | ||
|
|
e5567f2f46 | ||
|
|
80f545f3d5 | ||
|
|
06f0cc70b8 | ||
|
|
cf6b7544b0 | ||
|
|
d511c6334a | ||
|
|
0224fd6d54 | ||
|
|
e95ae8dbcf | ||
|
|
5fbd64da71 | ||
|
|
b282a2a621 | ||
|
|
9a7a534051 | ||
|
|
52fd030b6e | ||
|
|
dfe530a764 | ||
|
|
b079956075 | ||
|
|
e30037c4d1 | ||
|
|
eda7be627c | ||
|
|
af0a649656 | ||
|
|
8b6a3c4236 | ||
|
|
0988a92d8a | ||
|
|
9cf67764b7 | ||
|
|
6c4e1d1c41 | ||
|
|
b1cd1ea8b3 | ||
|
|
5169ed494c | ||
|
|
824211c31b | ||
|
|
be3dd83bc7 | ||
|
|
c2911c1e48 | ||
|
|
9a66c5c07d | ||
|
|
34d393b986 | ||
|
|
d4da02318d | ||
|
|
47162af6d5 | ||
|
|
8cd6a72dd3 | ||
|
|
ba0a183b6f | ||
|
|
73781c7edb | ||
|
|
6d99852c81 | ||
|
|
9325c726fd | ||
|
|
947bb8530e | ||
|
|
2b32f316ee | ||
|
|
4b1f23a189 | ||
|
|
71d220e7a4 | ||
|
|
747d0d0d17 | ||
|
|
5c72e6d335 | ||
|
|
e1ac2b0e10 | ||
|
|
0ba94f3bc9 | ||
|
|
ba0bfafcd5 | ||
|
|
81adb80b7e | ||
|
|
cb238ef170 | ||
|
|
b9b921de82 | ||
|
|
d10e31c278 | ||
|
|
fd641d77c7 | ||
|
|
2aa9710dd1 | ||
|
|
4afb2ef2b8 | ||
|
|
a54e6e7c4b | ||
|
|
7af26a356f | ||
|
|
2f66165f8c | ||
|
|
e86ce8fc38 | ||
|
|
9b48c65129 | ||
|
|
434cd133df | ||
|
|
aa91e4cdee | ||
|
|
d57c1e7ff0 | ||
|
|
13e97703e5 | ||
|
|
c597b293b8 | ||
|
|
6620d64ce7 | ||
|
|
2ae120c878 | ||
|
|
7a25035fb1 | ||
|
|
bf4052b550 | ||
|
|
5ca5ad2cee | ||
|
|
2848f1e13c | ||
|
|
f7895823cb | ||
|
|
b841c5c5e5 | ||
|
|
a7952a4633 | ||
|
|
d047d26dbf | ||
|
|
ddedc1640f | ||
|
|
021d7e5efc | ||
|
|
332e528012 | ||
|
|
a0155da06b | ||
|
|
d58d22adbe | ||
|
|
653352bff0 | ||
|
|
4f5b33d8df | ||
|
|
7e7d83ac36 | ||
|
|
0de5c043bb | ||
|
|
ec2769ea3c | ||
|
|
3f742f5f8e | ||
|
|
50849101d4 | ||
|
|
1ccf4a74c9 | ||
|
|
7cd4967963 | ||
|
|
21e2700da5 | ||
|
|
395a68ad80 | ||
|
|
b7f0132675 | ||
|
|
4ac827b9e8 | ||
|
|
d96963862e | ||
|
|
53217b061d | ||
|
|
4770daa7c6 | ||
|
|
2b709ad094 | ||
|
|
378b81b13b | ||
|
|
56ee72214f | ||
|
|
2e7c3167f5 | ||
|
|
a2fb728d3b | ||
|
|
be1c936e90 | ||
|
|
af8037ab03 | ||
|
|
3b8dc98226 | ||
|
|
b411b4e61b | ||
|
|
436eb30490 | ||
|
|
09b8087787 | ||
|
|
4ac4c69820 | ||
|
|
3ebc5c0865 | ||
|
|
7b94f8f105 | ||
|
|
ec994b3e97 | ||
|
|
86ae0182fd | ||
|
|
bfa891f0ca | ||
|
|
ef843cac63 | ||
|
|
78d68d0a4f | ||
|
|
4bceba777d | ||
|
|
2e537b1e5e | ||
|
|
df8463b625 | ||
|
|
f40371e0cf | ||
|
|
a565e7aed6 | ||
|
|
c948361090 | ||
|
|
4cf2b74a01 | ||
|
|
de9c8362ac | ||
|
|
b5bb5d35e7 | ||
|
|
d910cfa919 | ||
|
|
5857e3d5c6 | ||
|
|
25daa343c6 | ||
|
|
85224c8f0c | ||
|
|
5f8583e3eb | ||
|
|
062821d267 | ||
|
|
1fdaaf82d2 | ||
|
|
e4c1b17810 | ||
|
|
70057542c1 | ||
|
|
f2255ee707 | ||
|
|
791cc70b09 | ||
|
|
24dcb9973b | ||
|
|
adfd0d5c18 | ||
|
|
cfb128acb8 | ||
|
|
1e8e246ffb | ||
|
|
6162244730 | ||
|
|
86cbdf2442 | ||
|
|
5334626efb | ||
|
|
708d473b47 | ||
|
|
ead954ddaa | ||
|
|
294f511b9a | ||
|
|
b5ebcc3e07 | ||
|
|
7b8e7ac5c2 | ||
|
|
904d39beac | ||
|
|
8958b61fdd | ||
|
|
09293f7d9a | ||
|
|
d520c3d674 | ||
|
|
430e616328 | ||
|
|
976ad5fcee | ||
|
|
4d58ee2162 | ||
|
|
03b6abe5ec | ||
|
|
4479304f3f | ||
|
|
ed83405254 | ||
|
|
b815d945d9 | ||
|
|
2b8e024f48 | ||
|
|
2a0399b98d | ||
|
|
4e333e2d75 | ||
|
|
4f25b7bbbe | ||
|
|
3678db649b | ||
|
|
9d8b1fd99b | ||
|
|
7b62c06be3 | ||
|
|
bc978a91e3 | ||
|
|
611ba97b60 | ||
|
|
39dff1ea7c | ||
|
|
bcf7bcba1d | ||
|
|
8f00730189 | ||
|
|
1dde79ace2 | ||
|
|
5954fb91be | ||
|
|
e192383662 | ||
|
|
4a20fad4e5 | ||
|
|
a469e0864e | ||
|
|
cfce2bdbd9 | ||
|
|
529b5c0a00 | ||
|
|
7cc5787779 | ||
|
|
48eeb279e2 | ||
|
|
d67566252a | ||
|
|
8a2d79e17d | ||
|
|
17370dff54 | ||
|
|
f08a8e7634 | ||
|
|
83543bbddc | ||
|
|
b898c90f41 | ||
|
|
1ec927cf4f | ||
|
|
aef21ba9d5 | ||
|
|
0b31709aee | ||
|
|
7c3433256a | ||
|
|
c79c638f35 | ||
|
|
a148941a39 | ||
|
|
23abe26b0b | ||
|
|
8f21d6dde0 | ||
|
|
b74a6c9e03 | ||
|
|
b63f73ef93 | ||
|
|
1f0b369a15 | ||
|
|
7a43473513 | ||
|
|
07f367a2a5 | ||
|
|
c52ad68d92 | ||
|
|
85467dbd2a | ||
|
|
aa767846f0 | ||
|
|
6d3ad15d20 | ||
|
|
7b95db4d13 | ||
|
|
160b3ff655 | ||
|
|
757a39a75e | ||
|
|
4c08a527be | ||
|
|
578b7b0512 | ||
|
|
45e0259099 | ||
|
|
1ac1933ec1 | ||
|
|
43353e89bb | ||
|
|
010e4610f7 | ||
|
|
ba38853406 | ||
|
|
4944a9e51e | ||
|
|
73ad97859c | ||
|
|
e600da229c | ||
|
|
273b4f20db | ||
|
|
9843dccdf0 | ||
|
|
60ae1ec1e8 | ||
|
|
650c6670f2 | ||
|
|
d6dcba7a60 | ||
|
|
5b3849082f | ||
|
|
5de988a2e6 | ||
|
|
26220b2f54 | ||
|
|
8d8436f1f1 | ||
|
|
93057d9449 | ||
|
|
2addcc3ab5 | ||
|
|
b2419abdc8 | ||
|
|
9f654918ae | ||
|
|
cb086fe08d | ||
|
|
1482166ba3 | ||
|
|
6b8dd9bf03 | ||
|
|
a8af9da249 | ||
|
|
e410f82fdb | ||
|
|
4f70f228f1 | ||
|
|
ddc97b99fa | ||
|
|
5f42918cc9 | ||
|
|
e6dfe6fe89 | ||
|
|
fdbe693139 | ||
|
|
0633b2876c | ||
|
|
ee45ed8ec2 | ||
|
|
0c6726e3f7 | ||
|
|
2a75995189 | ||
|
|
9f074c1ab7 | ||
|
|
2917ee3b9d | ||
|
|
03f631f537 | ||
|
|
fb4171ae6f | ||
|
|
aec7d5aff7 | ||
|
|
045c225c8c | ||
|
|
c49ef49371 | ||
|
|
9fd012229b | ||
|
|
bb27099f9e | ||
|
|
411634ecda | ||
|
|
bb59f66008 | ||
|
|
b8a0f34c33 | ||
|
|
784955cdbd | ||
|
|
83764ce3d5 | ||
|
|
438cf271c0 | ||
|
|
8631eacdf3 | ||
|
|
6e7ce10585 | ||
|
|
3d4a58cb96 | ||
|
|
6fde3f8453 | ||
|
|
016d2f7ab1 | ||
|
|
a8ffc414fe | ||
|
|
b0c1c6e880 | ||
|
|
d3e6177a2b | ||
|
|
8adbdcc675 | ||
|
|
d1d7ff0d0a | ||
|
|
4b05a49b46 | ||
|
|
0309a866c8 | ||
|
|
bbc77f81ca | ||
|
|
483f6f2188 | ||
|
|
e0ef3ca39a | ||
|
|
aef29ff6c5 | ||
|
|
ec7bd8c1c8 | ||
|
|
fe0d69739d | ||
|
|
448e3a4639 | ||
|
|
2e74d6f652 | ||
|
|
237108a6d1 | ||
|
|
ed3a222ed4 | ||
|
|
8a696f6c52 | ||
|
|
184aa4fd20 | ||
|
|
ce14b9e43e | ||
|
|
1cdedb89f1 | ||
|
|
18efb098f9 | ||
|
|
fb3a27c02f | ||
|
|
6d41bcd511 | ||
|
|
e6b981fc38 | ||
|
|
74a3ea2a01 | ||
|
|
4bfb7a5d29 | ||
|
|
55ba040e3d | ||
|
|
e4e78e2083 | ||
|
|
82f25cc2e5 | ||
|
|
7b67c9aa8d | ||
|
|
854f3b5257 | ||
|
|
e37bc99c36 | ||
|
|
52121b90ff | ||
|
|
3eb9de57ef | ||
|
|
4ed501f4d4 | ||
|
|
0bb8ae540d | ||
|
|
963957f7a6 | ||
|
|
93a152068d | ||
|
|
bb318aefd2 | ||
|
|
0cafef7ab4 | ||
|
|
055e54966d | ||
|
|
c58254f951 | ||
|
|
ce710134ac | ||
|
|
fa68bdc82b | ||
|
|
cd9f2ab232 | ||
|
|
91136fe48f | ||
|
|
40cc0f721d | ||
|
|
72567c43e6 | ||
|
|
db82d7a6b3 | ||
|
|
c469a61908 | ||
|
|
1c4bcc5697 | ||
|
|
0f3b0309e8 | ||
|
|
5a393de1cb | ||
|
|
1617a61b33 | ||
|
|
ccd18c4f10 | ||
|
|
e287f5774a | ||
|
|
2dec3195ac | ||
|
|
2bea6694c7 | ||
|
|
38c468a204 | ||
|
|
6a80258163 | ||
|
|
55fe2600b7 | ||
|
|
cad4e183c5 | ||
|
|
ba447d2b00 | ||
|
|
fa5e32c46b | ||
|
|
e51b7c22a6 | ||
|
|
0473cdc5e1 | ||
|
|
36c65e8b5f | ||
|
|
d0e81b7778 | ||
|
|
7df63c87c9 | ||
|
|
0021f7b4ba | ||
|
|
540283970a | ||
|
|
5a2b9b60e6 | ||
|
|
12e09269d3 | ||
|
|
bcad5eda81 | ||
|
|
09ad56056e | ||
|
|
0c8dc63085 | ||
|
|
68e3bcba05 | ||
|
|
75a6111a9b | ||
|
|
1d9f970169 | ||
|
|
7cc328ed3b | ||
|
|
9a1c88c80d | ||
|
|
b1446438fb | ||
|
|
6ba1d71a81 | ||
|
|
a4f885a2c5 | ||
|
|
a8a3c63f64 | ||
|
|
ace7b4e2af | ||
|
|
a2885a90b7 | ||
|
|
4457487e2a | ||
|
|
830bb5f70d | ||
|
|
9106f9676c | ||
|
|
df1690515c | ||
|
|
8e287da7b5 | ||
|
|
128ebe2cb2 | ||
|
|
404964dfde | ||
|
|
7f74e79253 | ||
|
|
11b5cc83dc | ||
|
|
bda9d1f8e2 | ||
|
|
32efeee4c7 | ||
|
|
8adbd90cc3 | ||
|
|
074fcaaa73 | ||
|
|
d2209c8ead | ||
|
|
31f1079dfa | ||
|
|
2b87c83fa7 | ||
|
|
1c31c1947c | ||
|
|
50a8a83408 | ||
|
|
020fa4eefa | ||
|
|
64cf02bc45 | ||
|
|
06bd964adc | ||
|
|
2218b7b853 | ||
|
|
f6c45cd85a | ||
|
|
cf4907011b | ||
|
|
dea85add5d | ||
|
|
aa3325ef35 | ||
|
|
8059ba7306 | ||
|
|
c346c4178d | ||
|
|
f2555563a8 | ||
|
|
b948961d52 | ||
|
|
43987d844f | ||
|
|
cc3b14dbf8 | ||
|
|
3cc2d3260e | ||
|
|
be155f8c6c | ||
|
|
74952cf62d | ||
|
|
4a71df7859 | ||
|
|
7d7ea4c34b | ||
|
|
026d22f7f9 | ||
|
|
babd351151 | ||
|
|
d11894fd74 | ||
|
|
6072eab01d | ||
|
|
64e4c77428 | ||
|
|
48b70d0254 | ||
|
|
db2015f826 | ||
|
|
f893cf268b | ||
|
|
ad12d42b35 | ||
|
|
63a0c7c10b | ||
|
|
99d4d5ed9a | ||
|
|
7910c28be7 | ||
|
|
32e1ade388 | ||
|
|
a8aac7e1b4 | ||
|
|
7404aaf4d2 | ||
|
|
2f25bd476c | ||
|
|
681e4ae7fc | ||
|
|
e1ed0d2b09 | ||
|
|
32567a1c66 | ||
|
|
25753af331 | ||
|
|
6babcf9536 | ||
|
|
9a05f659f1 | ||
|
|
ca58bf661d | ||
|
|
62695d4d9a | ||
|
|
d723ef0a13 | ||
|
|
18160818d5 | ||
|
|
83bfaabd95 | ||
|
|
e23ac8496e | ||
|
|
2b7d05df6b | ||
|
|
606cb249e8 | ||
|
|
0ae739b437 | ||
|
|
f18e57b50e | ||
|
|
4c92c582d7 | ||
|
|
7b1bfeaca1 | ||
|
|
dd1d0d1cb8 | ||
|
|
995bd4953b | ||
|
|
32b61ac730 | ||
|
|
19bd54f425 | ||
|
|
76680777ff | ||
|
|
75b9a8a826 | ||
|
|
f439541844 | ||
|
|
65fb21a062 | ||
|
|
aa5df3c309 | ||
|
|
788394a1c8 | ||
|
|
7286c2f603 | ||
|
|
aff230ec7e | ||
|
|
3d538d0566 | ||
|
|
a1b287bd9d | ||
|
|
1d187f525d | ||
|
|
6c9ab0c019 | ||
|
|
e7157a8528 | ||
|
|
e2182344a2 | ||
|
|
9ebd882c3f | ||
|
|
f017b6b92f | ||
|
|
1c24c4e776 | ||
|
|
758e94a8ec | ||
|
|
9a7199bf7f | ||
|
|
1c691fa2c2 | ||
|
|
b435f49611 | ||
|
|
bcd7b2552e | ||
|
|
e76126fdd9 | ||
|
|
3fe55e7395 | ||
|
|
98e11c16fa | ||
|
|
0c5b2d302b | ||
|
|
0d526a2c4f | ||
|
|
0a8d49c233 | ||
|
|
4618d3e38a | ||
|
|
5521785b87 | ||
|
|
f12b404d58 | ||
|
|
6924e3a7d7 | ||
|
|
6fbe459903 | ||
|
|
0a245e5d65 | ||
|
|
2ed593c5e0 | ||
|
|
ce934b8998 | ||
|
|
aac4d3eefb | ||
|
|
176dc17c70 | ||
|
|
553bbda769 | ||
|
|
d4fd2d9ae0 | ||
|
|
c20138c9fc | ||
|
|
7f1ff11a4c | ||
|
|
ed9121a06f | ||
|
|
c110611d82 | ||
|
|
cc222a6f4a | ||
|
|
8293709f34 | ||
|
|
707740b7f1 | ||
|
|
98de661868 | ||
|
|
64df057e2f | ||
|
|
9d9c5cff75 | ||
|
|
3ed6f360da | ||
|
|
9f07f6441a | ||
|
|
fc0b326443 | ||
|
|
4ac0483ec6 | ||
|
|
724e78d4d5 | ||
|
|
6efa0490ea | ||
|
|
99e032eeb7 | ||
|
|
c80c212a54 | ||
|
|
95460038cf | ||
|
|
586e3b891c | ||
|
|
4402645b37 | ||
|
|
42624246bd | ||
|
|
ddd39ea0c9 | ||
|
|
a95cde372c | ||
|
|
dcbf03c355 | ||
|
|
ce44fb29c9 | ||
|
|
35f5df63e2 | ||
|
|
60c9200c06 | ||
|
|
25dacccb3b | ||
|
|
c0d08d966a | ||
|
|
cdfae2e8cb | ||
|
|
a3b0c351d0 | ||
|
|
92719d095a | ||
|
|
b2dc397fb0 | ||
|
|
55e09529c1 | ||
|
|
aed91d42bf | ||
|
|
eef58f4da0 | ||
|
|
4c427116a8 | ||
|
|
00e9624670 | ||
|
|
b52878fc97 | ||
|
|
68d9984d64 | ||
|
|
c4d293143d | ||
|
|
b08d91a218 | ||
|
|
1c586d8811 | ||
|
|
f5e13c25a7 | ||
|
|
6e267b8825 | ||
|
|
6cdfe70ac9 | ||
|
|
e8a0c693c9 | ||
|
|
ac7db87592 | ||
|
|
3d37a9eb8b | ||
|
|
8c5613b182 | ||
|
|
9e068335b6 | ||
|
|
19ed98f265 | ||
|
|
2aff36a1c0 | ||
|
|
eac7fccbb4 | ||
|
|
7562eaeb5f | ||
|
|
7b38683985 | ||
|
|
27763afbbe | ||
|
|
7a2b6f63c9 | ||
|
|
2ad8413c6d | ||
|
|
956c9000b8 | ||
|
|
838c2994a9 | ||
|
|
9974095659 | ||
|
|
85e289fd4e | ||
|
|
fa17e7019a | ||
|
|
edcb24068a | ||
|
|
38ead4909e | ||
|
|
06b2eb7662 | ||
|
|
5685f2699b | ||
|
|
c6916fd71e | ||
|
|
df64abaf9c | ||
|
|
6ed717556a | ||
|
|
e8c64074f3 | ||
|
|
4af13058f0 | ||
|
|
f3f223fa22 | ||
|
|
e244b4b263 | ||
|
|
ab09810bef | ||
|
|
fbcde465ad | ||
|
|
0f0d168976 | ||
|
|
33d15ca66d | ||
|
|
4fad97e8b1 | ||
|
|
81071771d9 | ||
|
|
4db3bafeda | ||
|
|
29aff298b8 | ||
|
|
4271289db0 | ||
|
|
9818e7c101 | ||
|
|
121a865bb8 | ||
|
|
7af340de85 | ||
|
|
cdc0dee6f5 | ||
|
|
4cbefff8b4 | ||
|
|
acc562bd7f | ||
|
|
44b87dca2a | ||
|
|
be98f786b3 | ||
|
|
6f215cbff9 | ||
|
|
96a29e91e8 | ||
|
|
e01a1a456c | ||
|
|
2561a6e00e | ||
|
|
e0bee0e0db | ||
|
|
7910d006ca | ||
|
|
778a651f7b | ||
|
|
2267dcb768 | ||
|
|
21e7c78744 | ||
|
|
54418957b6 | ||
|
|
77ce31128c | ||
|
|
79ce990644 | ||
|
|
2262d44638 | ||
|
|
972601caf6 | ||
|
|
114ebac21b | ||
|
|
57a86cd836 | ||
|
|
a741b1b9ac | ||
|
|
5d64ea48ba | ||
|
|
db6c65c7e8 | ||
|
|
b68fc6187f | ||
|
|
766a190015 | ||
|
|
2c17551d50 | ||
|
|
739bc361bd | ||
|
|
100fc20928 | ||
|
|
f42e79f5cf | ||
|
|
04da989108 | ||
|
|
11017dd8dc | ||
|
|
8d0aa65ab2 | ||
|
|
8ffb9e6f11 | ||
|
|
b0487798c2 | ||
|
|
740b89258d | ||
|
|
9026921214 | ||
|
|
df2aac3946 | ||
|
|
829e004ee5 | ||
|
|
072384eede | ||
|
|
dcc8043cf6 | ||
|
|
62c9484208 | ||
|
|
ebadccbe25 | ||
|
|
8013716158 | ||
|
|
ed7904b673 | ||
|
|
b2e3d33c80 | ||
|
|
7d1b4d916e | ||
|
|
0fb45d4584 | ||
|
|
0008919631 | ||
|
|
6bf8d19775 | ||
|
|
2e3ef87c42 | ||
|
|
abdd5cf19b | ||
|
|
8a636736b4 | ||
|
|
8fe2529306 | ||
|
|
6db217387f | ||
|
|
d33172df11 | ||
|
|
206a83c935 | ||
|
|
6a0935960b | ||
|
|
3665a792d8 | ||
|
|
90a58a1f37 | ||
|
|
f81eaf62ff | ||
|
|
8567f9b19f | ||
|
|
e33fc1a89b | ||
|
|
872ec90654 | ||
|
|
921f5524e7 | ||
|
|
a65959de3f | ||
|
|
bc095d087c | ||
|
|
3db744269e | ||
|
|
701e2f819b | ||
|
|
88bf569752 | ||
|
|
7283f48b36 | ||
|
|
f677b63c5c | ||
|
|
b2e4172c12 | ||
|
|
e0bec82a1c | ||
|
|
6cf117b3d0 | ||
|
|
6bcf47fc4b | ||
|
|
6d968bbbd8 | ||
|
|
8b803faf72 | ||
|
|
d9bcb16d9e | ||
|
|
ead9f0b3c6 | ||
|
|
6168621a36 | ||
|
|
d48c257b19 | ||
|
|
d723bbe3b7 | ||
|
|
f68e9c7681 | ||
|
|
f7a59edf19 | ||
|
|
a4741e28c5 | ||
|
|
c48ca99817 | ||
|
|
2b6965f801 | ||
|
|
caec4f80bb | ||
|
|
9aa7672b17 | ||
|
|
8e03cf9fc8 | ||
|
|
8d9a050ad6 | ||
|
|
266e08817e | ||
|
|
6c3526d47e | ||
|
|
19adfd5116 | ||
|
|
485f7fdcef | ||
|
|
856afcf3bf | ||
|
|
934b5e344f | ||
|
|
82eb8f14aa | ||
|
|
caa9cc213d | ||
|
|
403325d9dd | ||
|
|
62b3ca2fb9 | ||
|
|
e6034223f8 | ||
|
|
da02bea945 | ||
|
|
dd308d9276 | ||
|
|
fce72cad55 | ||
|
|
901b23e5ae | ||
|
|
df4d4d36bc | ||
|
|
408c56bfcf | ||
|
|
ea75ddfa85 | ||
|
|
bda5619b97 | ||
|
|
f6fb3cc766 | ||
|
|
7c3cb118a4 | ||
|
|
d03565ad95 | ||
|
|
c0f5746d1d | ||
|
|
a686bec025 | ||
|
|
c0d36bb85e | ||
|
|
e6df8dcd2e | ||
|
|
6f3fd99130 | ||
|
|
b5f7106780 | ||
|
|
e2f6dcab01 | ||
|
|
ff2f218bf0 | ||
|
|
902ed6e287 | ||
|
|
a40b49f2ed | ||
|
|
fc3b55b135 | ||
|
|
07bd5c633e | ||
|
|
f70dc14340 | ||
|
|
3d49ab2ae0 | ||
|
|
a133c39167 | ||
|
|
c9c8e20511 | ||
|
|
509ff7e833 | ||
|
|
a7a5fdb7f5 | ||
|
|
cf9ef281ce | ||
|
|
6345335be2 | ||
|
|
a008ec3729 | ||
|
|
e7a48f3909 | ||
|
|
718553373f | ||
|
|
310772d7fe | ||
|
|
213e6fab57 | ||
|
|
b7dbed5cd4 | ||
|
|
b621f2c24d | ||
|
|
21b9b090f9 | ||
|
|
709121b07f | ||
|
|
bb02a17e44 | ||
|
|
fd40c58b9c | ||
|
|
b7a6f1249d | ||
|
|
71125f07cc | ||
|
|
8500f5b437 | ||
|
|
eb25d9d7af | ||
|
|
033c85fc4b | ||
|
|
1663731489 | ||
|
|
1f65d47811 | ||
|
|
897cba3d07 | ||
|
|
f0e2247f06 | ||
|
|
1470ec60db | ||
|
|
7c04792777 | ||
|
|
6e750a2af0 | ||
|
|
7640dab380 | ||
|
|
6c10908cb7 | ||
|
|
abd235f332 | ||
|
|
4a638e82cc | ||
|
|
156d39490e | ||
|
|
a39968868f | ||
|
|
e0587ad17b | ||
|
|
06106f3831 | ||
|
|
1e332ed075 | ||
|
|
cef56cb5f1 | ||
|
|
97762712e6 | ||
|
|
4575996995 | ||
|
|
9a4594227e | ||
|
|
399bec62c2 | ||
|
|
177d015fc6 | ||
|
|
afd62ceeeb | ||
|
|
44c088c96d | ||
|
|
cf65347cd8 | ||
|
|
9fe54c5d21 | ||
|
|
0210ef2764 | ||
|
|
c66982c9bc | ||
|
|
a88be30c3f | ||
|
|
155cd31ced | ||
|
|
52dc77a00f | ||
|
|
c5de56790a | ||
|
|
d97cb4c360 | ||
|
|
8e499d164a | ||
|
|
764e1ac35f | ||
|
|
69e4fcfc76 | ||
|
|
951071e7ba | ||
|
|
5e82700871 | ||
|
|
4c706a213a | ||
|
|
d0da1f43a9 | ||
|
|
4afc439169 | ||
|
|
e47690dd52 | ||
|
|
7859aa0ce8 | ||
|
|
710c192e39 | ||
|
|
50e179f7a8 | ||
|
|
980a9d0640 | ||
|
|
fdfbf54808 | ||
|
|
fe8b0d99e5 | ||
|
|
7e817a5808 | ||
|
|
23b68bd9f9 | ||
|
|
0a8bbc7d3a | ||
|
|
0f5200aaf8 | ||
|
|
82d7b9cf05 | ||
|
|
0f5fafac8d | ||
|
|
7fcfffba6f | ||
|
|
13e9ef4c34 | ||
|
|
ea04c7d5eb | ||
|
|
8bf29528e3 | ||
|
|
c6f65debcf | ||
|
|
370b39a139 | ||
|
|
7b7e64576f | ||
|
|
d8b717a879 | ||
|
|
3967017ebb | ||
|
|
d93862043a | ||
|
|
719276b32a | ||
|
|
bf13f06e68 | ||
|
|
9f770d08f5 | ||
|
|
e62c529c1f | ||
|
|
4e1fc9b031 | ||
|
|
7d175ad38d | ||
|
|
de2c56560e | ||
|
|
0db413cb18 | ||
|
|
7c6424e05c | ||
|
|
3c328a00b5 | ||
|
|
e36f13c595 | ||
|
|
38a5cc33e8 | ||
|
|
245a3a73d8 | ||
|
|
d57d7ec4d8 | ||
|
|
09a4e4a6a5 | ||
|
|
d9beb4b660 | ||
|
|
63507a5567 | ||
|
|
e42dc259d8 | ||
|
|
f59f8d1937 | ||
|
|
0387bde81b | ||
|
|
b8fe95b945 | ||
|
|
792a1652e3 | ||
|
|
4816bfa26d | ||
|
|
95d1680ac3 | ||
|
|
5e1e6aea16 | ||
|
|
00696943d1 | ||
|
|
c1d293f9f9 | ||
|
|
d4d61d10bc | ||
|
|
c09e5e6552 | ||
|
|
ab08732ba8 | ||
|
|
584a7eb9e2 | ||
|
|
d44b04f425 | ||
|
|
eeec04b21a | ||
|
|
0642bc28bc | ||
|
|
2b12b67582 | ||
|
|
d9b575b051 | ||
|
|
c6dbb41724 | ||
|
|
ea4c1bf5e5 | ||
|
|
67afc050b8 | ||
|
|
6e90da90f1 | ||
|
|
26872c829b | ||
|
|
34f32c8ba5 | ||
|
|
b0d847dc18 | ||
|
|
adabef9706 | ||
|
|
474adf2d53 | ||
|
|
4f23ad37f5 | ||
|
|
eb6b160377 | ||
|
|
0770e00d8d | ||
|
|
ab0e95177e | ||
|
|
cfd9e4b256 | ||
|
|
bfed0ed791 | ||
|
|
3cec9f7ee0 | ||
|
|
3a5dc62908 | ||
|
|
e0cdfcf403 | ||
|
|
439866216c | ||
|
|
a1b0731665 | ||
|
|
bcc216fd2e | ||
|
|
574d848828 | ||
|
|
22acc2307c | ||
|
|
d01a3c327e | ||
|
|
7c52b6ab23 | ||
|
|
23d27aee6b | ||
|
|
9315358fa6 | ||
|
|
83aaa94cfe | ||
|
|
7744099ee5 | ||
|
|
35bb6b62d8 | ||
|
|
99844931f4 | ||
|
|
d8c34a7cdf | ||
|
|
c284edcd81 | ||
|
|
f5d4b352b3 | ||
|
|
e44b31d755 | ||
|
|
3a28068b48 | ||
|
|
03433b00d2 | ||
|
|
6f39ebf48f | ||
|
|
949ca00dbb | ||
|
|
bfdf1ee8f5 | ||
|
|
e047d4e8a6 | ||
|
|
15cf2069b6 | ||
|
|
564b0ee0c4 | ||
|
|
b454932ff5 | ||
|
|
191c07b6a1 | ||
|
|
5b0deb9fc2 | ||
|
|
297ec64bde | ||
|
|
a9b85caeb7 | ||
|
|
e30b883148 | ||
|
|
cd6be0d755 | ||
|
|
95b74c5f2f | ||
|
|
45f26bb22e | ||
|
|
09b0e09932 | ||
|
|
ea9d88009f | ||
|
|
e5013c918e | ||
|
|
4b45103a4e | ||
|
|
1ea28411da | ||
|
|
e27fce1227 | ||
|
|
cf3b8378bd | ||
|
|
9f07532140 | ||
|
|
f1aedc619e | ||
|
|
5f778e706f | ||
|
|
ee15663679 | ||
|
|
c271d4bfe4 | ||
|
|
99188f7427 | ||
|
|
88a07ecf63 | ||
|
|
f1af2e35cd | ||
|
|
352e20f01b | ||
|
|
f7d10a6cf1 | ||
|
|
6a5808077c | ||
|
|
09be11b5a8 | ||
|
|
5117c1a528 | ||
|
|
ead9857203 | ||
|
|
cd3493e040 | ||
|
|
02c22c1894 | ||
|
|
8b41a03080 | ||
|
|
306f4f119b | ||
|
|
3dc28c7291 | ||
|
|
f2f2215059 | ||
|
|
f75179b2f6 | ||
|
|
3ead35c984 | ||
|
|
efe20a1fe8 | ||
|
|
ab1e56a14e | ||
|
|
055d6bf601 | ||
|
|
683c3e96d6 | ||
|
|
d8e414a4a2 | ||
|
|
9237d68b79 | ||
|
|
12a14fbe29 | ||
|
|
22e4e4efc1 | ||
|
|
588327fd68 | ||
|
|
d35ef2d360 | ||
|
|
5ce5193430 | ||
|
|
6e74215b2e | ||
|
|
884a9835c4 | ||
|
|
233f40f3e9 | ||
|
|
9b24e61033 | ||
|
|
286928d59e | ||
|
|
47cee5f6a1 | ||
|
|
6820b748f7 | ||
|
|
42422845b0 | ||
|
|
8ed11cdc07 | ||
|
|
006cd1ee89 | ||
|
|
49f7ee6042 | ||
|
|
9612e4d4e9 | ||
|
|
4b938db0c1 | ||
|
|
b0a079dce9 | ||
|
|
52069f35c5 | ||
|
|
140535d875 | ||
|
|
611084d00c | ||
|
|
1b7c810eeb | ||
|
|
dd2a698147 | ||
|
|
e99df58bce | ||
|
|
70139221ab | ||
|
|
5d8d2b4a34 | ||
|
|
f59f322226 | ||
|
|
acca3f37b5 | ||
|
|
0de62f620d | ||
|
|
351c4a00e9 | ||
|
|
b048d81ac0 | ||
|
|
b9c9a2b75f | ||
|
|
4cea22d813 | ||
|
|
edad4e7d35 | ||
|
|
e28acd8d2d | ||
|
|
86feaff82f | ||
|
|
5f747db224 | ||
|
|
ccda41e18e | ||
|
|
2bb83c6532 | ||
|
|
ec59faba8c | ||
|
|
c5d8c44b81 | ||
|
|
7a4e4629e5 | ||
|
|
4d3dd95a60 | ||
|
|
713021359e | ||
|
|
927d2c36e8 | ||
|
|
afc6368915 | ||
|
|
3f604ad7b5 | ||
|
|
c5d4e237ad | ||
|
|
0b50b21779 | ||
|
|
cafc70f1c9 | ||
|
|
b4377c346f | ||
|
|
7c206af757 | ||
|
|
6212d0a052 | ||
|
|
8909822aea | ||
|
|
5fc0964f73 | ||
|
|
7725559a44 | ||
|
|
03414a58e9 | ||
|
|
ade0419bf6 | ||
|
|
9d646b2eed | ||
|
|
0d718a5ca2 | ||
|
|
78c8b36dfa | ||
|
|
b5ca0874fa | ||
|
|
e209becff9 | ||
|
|
652d42aa6b | ||
|
|
058a11597f | ||
|
|
dd73d6e19a | ||
|
|
3793f618a8 | ||
|
|
6621983a9c | ||
|
|
e0b0302f5a | ||
|
|
b02584bec6 | ||
|
|
c832d61409 | ||
|
|
ac701f28b5 | ||
|
|
53032140e7 | ||
|
|
3c7633564f | ||
|
|
43eae40404 | ||
|
|
e34b1b54d5 | ||
|
|
0658a7de0d | ||
|
|
9e0a4dfac8 | ||
|
|
82034ade90 | ||
|
|
b979203e12 | ||
|
|
c9a368f13a | ||
|
|
cfabf22fef | ||
|
|
61aef77916 | ||
|
|
8b6c6beceb | ||
|
|
4688e49454 | ||
|
|
76cc28b215 | ||
|
|
467e5423d2 | ||
|
|
422bf7b689 | ||
|
|
1d230bd4aa | ||
|
|
029850842b | ||
|
|
cc65555c3d | ||
|
|
3b090396a4 | ||
|
|
2e4656ae8b | ||
|
|
ebce63baa4 | ||
|
|
08f691ee0d | ||
|
|
7550a19951 | ||
|
|
daeb76df11 | ||
|
|
31315e5ba6 | ||
|
|
e4e1fa6a4c | ||
|
|
6e36312be8 | ||
|
|
9cc7ba1d82 | ||
|
|
9182001147 | ||
|
|
5d1510083e | ||
|
|
7035f4cc1f | ||
|
|
da826b2a22 | ||
|
|
a152d8664d | ||
|
|
6ad75d963a | ||
|
|
e92d34a9e3 | ||
|
|
768066db58 | ||
|
|
886b63e55b | ||
|
|
3c7ee65b7d | ||
|
|
0e060c4564 | ||
|
|
3e127dbd9c | ||
|
|
ad66d14a1b | ||
|
|
5db61b78ea | ||
|
|
85f9597f2c | ||
|
|
73aca22605 | ||
|
|
077343ca20 | ||
|
|
c76946c770 | ||
|
|
2344a49260 | ||
|
|
65f463dbbd | ||
|
|
6e100a70b9 | ||
|
|
77650ebe09 | ||
|
|
cdba2bd9e2 | ||
|
|
e84cc92fcd | ||
|
|
a2890948bb | ||
|
|
f2e1ebef45 | ||
|
|
3c33614115 | ||
|
|
fcc4b1e8ee | ||
|
|
c8ac4ec1e8 | ||
|
|
93ab8b88d4 | ||
|
|
cc12c302e3 | ||
|
|
73941ca30e | ||
|
|
f963711820 | ||
|
|
1a0a301fb6 | ||
|
|
f87a9b7aae | ||
|
|
f18f0bc762 | ||
|
|
c3b1bc6674 | ||
|
|
e9bb228528 | ||
|
|
a906957454 | ||
|
|
49d10337a1 | ||
|
|
87c6c31f1f | ||
|
|
5f3639e396 | ||
|
|
14d36ef8dc | ||
|
|
fc186f1718 | ||
|
|
70aa93fe8d | ||
|
|
901df22d39 | ||
|
|
cd0fe3d6d4 | ||
|
|
09f2c3f645 | ||
|
|
116542d8e4 | ||
|
|
ffae6e269b | ||
|
|
2194dc0463 | ||
|
|
63e3923349 | ||
|
|
da2d426137 | ||
|
|
8df7672624 | ||
|
|
b9dba61c97 | ||
|
|
1872a1a77c | ||
|
|
cf57bad7fa | ||
|
|
7e956894ee | ||
|
|
f78046d4c1 | ||
|
|
beb09cbf4a | ||
|
|
6b2a5d7777 | ||
|
|
4b0d368222 | ||
|
|
5ab2f6ea3a | ||
|
|
f845bf5b25 | ||
|
|
bb10d64d58 | ||
|
|
96e8e9736f | ||
|
|
62472b97e6 | ||
|
|
10b89ff00b | ||
|
|
2d8339343f | ||
|
|
9ddd88b5d0 | ||
|
|
614ec645c6 | ||
|
|
893ffc48dc | ||
|
|
7bddb70f11 | ||
|
|
9d4a38e560 | ||
|
|
9d20b31585 | ||
|
|
fb8838375f | ||
|
|
dead4e3f85 | ||
|
|
9c833a8a95 | ||
|
|
0b90794bca | ||
|
|
fd89757814 | ||
|
|
5eedc312c8 | ||
|
|
36c1cc5e0c | ||
|
|
0b4cdea9dd | ||
|
|
12172168f6 | ||
|
|
c79aa49252 | ||
|
|
b869308bf4 | ||
|
|
7a748bd142 | ||
|
|
d11669e613 | ||
|
|
b5108b938c | ||
|
|
049d510536 | ||
|
|
113f7b18a6 | ||
|
|
65b248c3cf | ||
|
|
1698996ac3 | ||
|
|
2ae47f2375 | ||
|
|
6733d12e81 | ||
|
|
8b5109e32f | ||
|
|
7b651d56b1 | ||
|
|
23b9b8aaf6 | ||
|
|
8d9fb43828 | ||
|
|
0d1be72fdb | ||
|
|
ce102a2af6 | ||
|
|
dda6071bbc | ||
|
|
0c4809bb16 | ||
|
|
4b87c5e80e | ||
|
|
b8fe9b9074 | ||
|
|
bc8b18cea8 | ||
|
|
20fdd3e891 | ||
|
|
ff9147487f | ||
|
|
af494f3b5e | ||
|
|
f68d26317d | ||
|
|
5b881f7c77 | ||
|
|
fe5efff255 | ||
|
|
46d18a642c | ||
|
|
c00012f28b | ||
|
|
d0050f7d59 | ||
|
|
f3a4881261 | ||
|
|
1728909964 | ||
|
|
f6e7460b24 | ||
|
|
351829888f | ||
|
|
e520c30ade | ||
|
|
99267ac2f0 | ||
|
|
6f1f7ef7fc | ||
|
|
d647e947e2 | ||
|
|
adef45c30f | ||
|
|
8b80859023 | ||
|
|
907fff89c8 | ||
|
|
ca532ffdaf | ||
|
|
64ee842dc4 | ||
|
|
9c0754a88d | ||
|
|
3dac9e3dae | ||
|
|
6963e667aa | ||
|
|
af292b291b | ||
|
|
918f75704e | ||
|
|
6e258e5d0c | ||
|
|
a8ea4ec085 | ||
|
|
2f894af028 | ||
|
|
dd400e1214 | ||
|
|
f8a183bfdf | ||
|
|
2404193b61 | ||
|
|
3ef11f5b26 | ||
|
|
30419cec12 | ||
|
|
162841feb8 | ||
|
|
f910f63f8d | ||
|
|
860cb75f8f | ||
|
|
293be93b93 | ||
|
|
976e77b31e | ||
|
|
9f28ee2982 | ||
|
|
e6c1e48b86 | ||
|
|
2c2880199d | ||
|
|
c7f8737eeb | ||
|
|
f3aea3108d | ||
|
|
e4f8ad0f05 | ||
|
|
8b80f72313 | ||
|
|
06ebce31ca | ||
|
|
59db8ba997 | ||
|
|
df37d4aa7f | ||
|
|
fd9935c800 | ||
|
|
c057430488 | ||
|
|
767fa78e22 | ||
|
|
a0e289dcd6 | ||
|
|
c62d147254 | ||
|
|
f739bee353 | ||
|
|
db4dfb69fb | ||
|
|
9729a5ef16 | ||
|
|
6f12818352 | ||
|
|
75fc318da0 | ||
|
|
acf71cc2c2 | ||
|
|
42efe10473 | ||
|
|
b77e72880b | ||
|
|
297c1524b4 | ||
|
|
6140743769 | ||
|
|
d5ca700828 | ||
|
|
a7e1fe76c3 | ||
|
|
f6c55279d1 | ||
|
|
826f4835bb | ||
|
|
119a28def1 | ||
|
|
c1ffb8bc33 | ||
|
|
60cc9d14af | ||
|
|
7cf4f8fdaa | ||
|
|
1b84e83061 | ||
|
|
b0d2ee8760 | ||
|
|
2022432d35 | ||
|
|
c0ed335d84 | ||
|
|
4964562866 | ||
|
|
f78dc443ad | ||
|
|
56b47db778 | ||
|
|
2d4bf17b28 | ||
|
|
3b1819c68d | ||
|
|
575bbdb53b | ||
|
|
2fa7482028 | ||
|
|
e5062317e2 | ||
|
|
543f4fa3c2 | ||
|
|
49cdf69815 | ||
|
|
04196a1a19 | ||
|
|
b8622b8708 | ||
|
|
a2feca0ba3 | ||
|
|
ae6c6a75b2 | ||
|
|
bd987e9531 | ||
|
|
24d69fd820 | ||
|
|
9e2fa8d7f1 | ||
|
|
74ba2f7283 | ||
|
|
7603b5c63f | ||
|
|
03566afe66 | ||
|
|
3c2e314e2d | ||
|
|
1fcf08b5cd | ||
|
|
c13db89ed4 | ||
|
|
e68ae9e8a6 | ||
|
|
fb252d6db3 | ||
|
|
df959353d6 | ||
|
|
fbc443483d | ||
|
|
00f1dbc3dd | ||
|
|
c6021ff012 | ||
|
|
7a8cfcee35 | ||
|
|
c15586a1c4 | ||
|
|
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 | ||
|
|
a16bc5db28 | ||
|
|
51657338f5 | ||
|
|
0fe3ea25af | ||
|
|
806daaf7c9 | ||
|
|
21ff4a83b5 | ||
|
|
ecc9aa40d7 | ||
|
|
c34ca2a59b | ||
|
|
dde7094fe3 | ||
|
|
105afdfefc | ||
|
|
4c254a8686 | ||
|
|
33ca203e57 | ||
|
|
2ff4133cd4 | ||
|
|
49ad85713e | ||
|
|
1978bf151f | ||
|
|
506378de82 | ||
|
|
53ead7a03f | ||
|
|
ab979fd63c | ||
|
|
1e84a2238b | ||
|
|
5618adf86c | ||
|
|
345b0bc95f | ||
|
|
b1d6c35e99 | ||
|
|
d767f1a101 | ||
|
|
c15f2b9a12 | ||
|
|
a21a53662b | ||
|
|
ebb5340019 | ||
|
|
6108bd214e | ||
|
|
eed27e101a | ||
|
|
2767680bed | ||
|
|
4080e30b6f | ||
|
|
e89257be62 | ||
|
|
d3c40a7e8b | ||
|
|
60657d5d25 | ||
|
|
71d5ae0e68 | ||
|
|
46b8d202c7 | ||
|
|
bc193a6470 | ||
|
|
577528fa0a | ||
|
|
35e78ea633 | ||
|
|
d5d4f85003 | ||
|
|
c6a49a6f5d | ||
|
|
a3ff9373a2 | ||
|
|
e6d2e74b17 | ||
|
|
e8464206e7 | ||
|
|
b6c4934123 | ||
|
|
8ee83f738b | ||
|
|
c359300375 | ||
|
|
eb3361fa07 | ||
|
|
1b53da6749 | ||
|
|
c701895e29 | ||
|
|
e81222821e | ||
|
|
cbca8c9e93 | ||
|
|
92c4edacf2 | ||
|
|
e4c648ee92 | ||
|
|
69f6586aa9 | ||
|
|
391713b84d | ||
|
|
8c6a25e4b4 | ||
|
|
250701aff0 | ||
|
|
2dfd1bd5a2 | ||
|
|
1c7380d4cf | ||
|
|
68d9ac88b3 | ||
|
|
f6b3874668 | ||
|
|
c616878a64 | ||
|
|
ad485a68ce | ||
|
|
2ebba3b8e7 | ||
|
|
4117e6e1c5 | ||
|
|
423ee04879 | ||
|
|
1625e3ba6c | ||
|
|
5a71ab3c20 | ||
|
|
b5833bf556 | ||
|
|
6c31765965 | ||
|
|
6dc1d42d90 | ||
|
|
e273a954e6 | ||
|
|
a602a942c4 | ||
|
|
dd32b33621 | ||
|
|
355d0405f4 | ||
|
|
c1e1595d58 | ||
|
|
ef476ac9b3 | ||
|
|
a7aaa7b18b | ||
|
|
3abfbab5d1 | ||
|
|
b158474e21 | ||
|
|
cfbcc41543 | ||
|
|
fec0a02b1c | ||
|
|
3d93ba1215 | ||
|
|
042083c0e7 | ||
|
|
e0bb7b7faf | ||
|
|
acc4099ac8 | ||
|
|
683ad8959a | ||
|
|
a559c771cf | ||
|
|
8ba0ebf955 | ||
|
|
d83ec1827a | ||
|
|
314e49f5e1 | ||
|
|
412b139796 | ||
|
|
9b29487934 | ||
|
|
95213e41c4 | ||
|
|
644ddfd5e1 | ||
|
|
68dd9900c4 | ||
|
|
6b100cf70b | ||
|
|
6f33e5e8a0 | ||
|
|
268e0e72a3 | ||
|
|
5380b12294 | ||
|
|
2ba1a95c8d | ||
|
|
1ad0ee4a71 | ||
|
|
fc12903cfd | ||
|
|
640d22484d | ||
|
|
34dc4d64e6 | ||
|
|
bbb547efb6 | ||
|
|
5b3640e23d | ||
|
|
0fbbe244d8 | ||
|
|
57def7da0c | ||
|
|
0fcf1c2732 | ||
|
|
c15b6cdf79 | ||
|
|
06e25e04f8 | ||
|
|
f8e04656cd | ||
|
|
1c8debd894 | ||
|
|
58d8fcd074 | ||
|
|
a70f1ee1e0 | ||
|
|
271ed3cbe2 | ||
|
|
8ddaf57e17 | ||
|
|
4f1ead116f | ||
|
|
3194c5b600 | ||
|
|
717f1a9b76 | ||
|
|
b7675a21eb | ||
|
|
b0d4c0d578 | ||
|
|
b7a1d2df75 | ||
|
|
5d31d33804 | ||
|
|
a97af42e4b | ||
|
|
17c6797afb | ||
|
|
c8129607e8 | ||
|
|
c2dce38bb1 | ||
|
|
8b0b7492f5 | ||
|
|
a25cfd87cc | ||
|
|
f9432acf1b | ||
|
|
c6c468c986 | ||
|
|
b92a888583 | ||
|
|
692b4b33fb | ||
|
|
79f427e10a | ||
|
|
340b3e7fe8 | ||
|
|
50a44c9416 | ||
|
|
3c41493d8e | ||
|
|
4566ea436c | ||
|
|
499bf3bc28 | ||
|
|
489a321763 | ||
|
|
9d86d923aa | ||
|
|
454529bd6a | ||
|
|
ca17dd3ca3 | ||
|
|
71c7a3de69 | ||
|
|
76fc689337 | ||
|
|
af5d25490a | ||
|
|
fb161ae783 | ||
|
|
b92b20e8d2 | ||
|
|
4b19059df1 | ||
|
|
baa6ec5cba | ||
|
|
1a86b80c61 | ||
|
|
3783da3647 | ||
|
|
39dfc00693 | ||
|
|
c7cad20aa7 | ||
|
|
5901365e0e | ||
|
|
6324aacba1 | ||
|
|
d51ba8f6dd | ||
|
|
9b69e135d9 | ||
|
|
c3218b2f5a | ||
|
|
9bbbff31f8 | ||
|
|
432429026b | ||
|
|
a47ecbdea9 | ||
|
|
f250aff99b | ||
|
|
003f14003e | ||
|
|
fe4e245cda | ||
|
|
668da62519 | ||
|
|
fd89254d5a | ||
|
|
b4338c1761 | ||
|
|
fb3c79617f | ||
|
|
b80fe428ac | ||
|
|
5806563ba4 | ||
|
|
5adecc307f | ||
|
|
12d1b5e849 | ||
|
|
3f2095870d | ||
|
|
1481c76a0d | ||
|
|
1cdc80e09b | ||
|
|
e568aa8320 | ||
|
|
28629aa836 | ||
|
|
19f180331b | ||
|
|
3292f0b545 | ||
|
|
3333bfeeff | ||
|
|
eb1ac3bc9b | ||
|
|
65ae1a6177 | ||
|
|
0fba385b9e | ||
|
|
ee65a54684 | ||
|
|
82fef82c4f | ||
|
|
70383a9b9d | ||
|
|
15fdba060c | ||
|
|
c1065dab2d | ||
|
|
dfb4afc698 | ||
|
|
893b09e7e4 | ||
|
|
938bcb2b62 | ||
|
|
ac45f67a21 | ||
|
|
36cd9664b1 | ||
|
|
073d330db4 | ||
|
|
9ba356c47e | ||
|
|
f2bec9b478 | ||
|
|
3d0cbdd1a7 | ||
|
|
c5f5bf0287 | ||
|
|
99986c1b94 | ||
|
|
7d669caa3c | ||
|
|
b68e3cb10f | ||
|
|
e305c488d4 | ||
|
|
a2417bbe56 | ||
|
|
b3967b36c0 | ||
|
|
5fb33dfee9 | ||
|
|
c002768e5b | ||
|
|
aff33c6a5d | ||
|
|
a84b497fae | ||
|
|
c33d1bcd3c | ||
|
|
4071e14a7e | ||
|
|
b5b3f190b7 | ||
|
|
d43a3e132c | ||
|
|
a90c21f80a | ||
|
|
2b768165e5 | ||
|
|
e8425ba03a | ||
|
|
b0a6f402e9 | ||
|
|
02e86a940b | ||
|
|
79d03eb43e | ||
|
|
b564955f85 | ||
|
|
5aed64f614 | ||
|
|
a823a4d9b7 | ||
|
|
4eed2193f4 | ||
|
|
48e7a41af6 | ||
|
|
bba5caecf7 | ||
|
|
ede6a45f15 | ||
|
|
9c65d23229 | ||
|
|
5dedfe9295 | ||
|
|
95d8c368c8 | ||
|
|
49fbfb8bbc | ||
|
|
aa3d2a5289 | ||
|
|
48ae6df4b7 | ||
|
|
c635351a12 | ||
|
|
d9ff77fd9a | ||
|
|
e1a7954307 | ||
|
|
efe6421133 | ||
|
|
79b62f4407 | ||
|
|
9d17804ac7 | ||
|
|
5986355504 | ||
|
|
192e6fde92 | ||
|
|
ad090e62cc | ||
|
|
5c072fea62 | ||
|
|
fd01a40810 | ||
|
|
7b9a83a273 | ||
|
|
f964e0e502 | ||
|
|
22acb7c74b | ||
|
|
6a99e81e75 | ||
|
|
93b6de1caf | ||
|
|
1fbab5db2b | ||
|
|
950e852dee | ||
|
|
826898e3fe | ||
|
|
1268149d83 | ||
|
|
908299970f | ||
|
|
861dde8627 | ||
|
|
cc9802a0d8 | ||
|
|
69d1f3aa53 | ||
|
|
ea4587d842 | ||
|
|
fb4c95f945 | ||
|
|
95a27af5f2 | ||
|
|
9d7b25ade6 | ||
|
|
3a8f4199cd | ||
|
|
11002efc02 | ||
|
|
367c1c3568 | ||
|
|
9e04230d99 | ||
|
|
21304db7c9 | ||
|
|
f4f6e98045 | ||
|
|
ad41eff38a | ||
|
|
dbd6cc4148 | ||
|
|
dda71e5ccd | ||
|
|
cfe8059176 | ||
|
|
8b00784ecc | ||
|
|
9bcc6bbad0 | ||
|
|
ce7995966d | ||
|
|
cea5f86df4 | ||
|
|
0912253b1b | ||
|
|
5aecc4be03 | ||
|
|
e09178c14c | ||
|
|
477ded6a4a | ||
|
|
311c48becb | ||
|
|
ec924a7ddf | ||
|
|
e39416a786 | ||
|
|
51b356cc0e | ||
|
|
66b13bdb8b | ||
|
|
4ade58da01 | ||
|
|
efcfc0783c | ||
|
|
aa22db7fe5 | ||
|
|
5e0f008b65 | ||
|
|
eaf840e1da | ||
|
|
fc9e47778b | ||
|
|
35edf78aed | ||
|
|
07718f0449 | ||
|
|
6759156519 | ||
|
|
e2688e6feb | ||
|
|
65ba6423b1 | ||
|
|
a2f8fe3694 | ||
|
|
5273a17ab6 | ||
|
|
f7c1e7b706 | ||
|
|
45bbc4c681 | ||
|
|
6af5682548 | ||
|
|
24ed06626d | ||
|
|
eeff4af167 | ||
|
|
17f46afe14 | ||
|
|
224618cf21 | ||
|
|
ea93ab2a83 | ||
|
|
b9f7c39550 | ||
|
|
86b4b8e43a | ||
|
|
f54d07548e | ||
|
|
037db8a3e4 | ||
|
|
8f00e85abd | ||
|
|
9ccc4c4059 | ||
|
|
8408f98693 | ||
|
|
cde271fd5b | ||
|
|
c21a097fd2 | ||
|
|
83c32d4963 | ||
|
|
22c2d56da0 | ||
|
|
bd8d6e0480 | ||
|
|
825eb700b1 | ||
|
|
e59ee70f88 | ||
|
|
1173a29ed5 | ||
|
|
6a2ff369ea | ||
|
|
e22606ae79 | ||
|
|
bf56c2a9fa | ||
|
|
6567b55ea3 | ||
|
|
20e90c0de4 | ||
|
|
2892d5ec6f | ||
|
|
e034811e92 | ||
|
|
ee18bbd145 | ||
|
|
e3ebbde767 | ||
|
|
6a57980439 | ||
|
|
765760f3a5 | ||
|
|
ab1ac7c995 | ||
|
|
99b0c9c079 | ||
|
|
e99ab431e1 | ||
|
|
1e1aaaccca | ||
|
|
318d6afb0e | ||
|
|
ba353857eb | ||
|
|
1fd42f343d | ||
|
|
ec9686cfb8 | ||
|
|
a1bff809f3 | ||
|
|
76fe155c0a | ||
|
|
0b0254aed9 | ||
|
|
9258c3849b | ||
|
|
d530f30bc9 | ||
|
|
7d8bbac04f | ||
|
|
7b0c0c3e17 | ||
|
|
298c3097f7 | ||
|
|
3a3f221418 | ||
|
|
78110791e1 | ||
|
|
95d4c3d0d5 | ||
|
|
e95b49ba8f | ||
|
|
92ccb7e463 | ||
|
|
2f34bf69e3 | ||
|
|
d093c03d92 | ||
|
|
d1ade8789b | ||
|
|
1291eb5b7c | ||
|
|
9c32937c83 | ||
|
|
1ec28e9825 | ||
|
|
86fce898e5 | ||
|
|
bbee87f7df | ||
|
|
811ddb9b44 | ||
|
|
de798da074 | ||
|
|
65d468be33 | ||
|
|
9664ff67f3 | ||
|
|
99f73cf31e | ||
|
|
a216e2b92e | ||
|
|
9dfd9ad519 | ||
|
|
97133510a7 | ||
|
|
43d166fb7d | ||
|
|
2f8a580854 | ||
|
|
a21a2ab3bb | ||
|
|
03106526e9 | ||
|
|
a9ac3917b3 | ||
|
|
53ff491efd | ||
|
|
1feb6ec452 | ||
|
|
df00f53e54 | ||
|
|
be32af7588 | ||
|
|
19be77ed49 | ||
|
|
1c43c095bc | ||
|
|
59850f4869 | ||
|
|
804b61ff95 | ||
|
|
d431c607ba | ||
|
|
b87b0489e9 | ||
|
|
2e593d44ee | ||
|
|
71354464e3 | ||
|
|
c48b4788c6 | ||
|
|
fe9a7333ed | ||
|
|
16d9e06db2 | ||
|
|
b40ee19735 | ||
|
|
d5b0356625 | ||
|
|
5ca77c3f64 | ||
|
|
54e9307795 | ||
|
|
60d7e45048 | ||
|
|
931559cca8 | ||
|
|
2567c2937d | ||
|
|
5b8e6d4df6 | ||
|
|
087c053bd5 | ||
|
|
7e699136d7 | ||
|
|
b7bbfe2a46 | ||
|
|
69783b0709 | ||
|
|
614041d55e | ||
|
|
856f61c2ee | ||
|
|
b0b196a522 | ||
|
|
62f04d239f | ||
|
|
4210d10fca | ||
|
|
d02842f0ea | ||
|
|
2543b7db79 | ||
|
|
41f430429b | ||
|
|
4ed4f8d942 | ||
|
|
cc5396801b | ||
|
|
a72dc36d67 | ||
|
|
50989e4e1e | ||
|
|
41487440e3 | ||
|
|
af72750354 | ||
|
|
a58dc49acc | ||
|
|
6dbb493d10 | ||
|
|
b8c37ff5d7 | ||
|
|
04319195c6 | ||
|
|
aadd78b7ac | ||
|
|
e23da1f5fb | ||
|
|
50eeaf8497 | ||
|
|
039202559f | ||
|
|
5419032e8d | ||
|
|
017a92c4bc | ||
|
|
a16040a595 | ||
|
|
3f6936a999 | ||
|
|
d3f3359f66 | ||
|
|
3f110aaabd | ||
|
|
4e884d57ca | ||
|
|
efbe0562f9 | ||
|
|
096dfea1a6 | ||
|
|
bd5a827593 | ||
|
|
404bcaddd4 | ||
|
|
d2d52a7eb3 | ||
|
|
1761c47713 | ||
|
|
82e97aa4fa | ||
|
|
e598178869 | ||
|
|
b6f89195ab | ||
|
|
7aa92c039a | ||
|
|
fff36949b7 | ||
|
|
c524f17978 | ||
|
|
e0a0497dd2 | ||
|
|
fce9220dcb | ||
|
|
a8ddb64b49 | ||
|
|
6d8df2661c | ||
|
|
1659de3a2b | ||
|
|
9752c72998 | ||
|
|
db2e3a518d | ||
|
|
94b20662a4 | ||
|
|
7bfc0998fd | ||
|
|
6707f0efdf | ||
|
|
c7fe5a538f | ||
|
|
9b20006938 | ||
|
|
0b258bd384 | ||
|
|
2302c17273 | ||
|
|
b619699637 | ||
|
|
34434e03fe | ||
|
|
29bd31f609 | ||
|
|
cf69f9e4c4 | ||
|
|
028c9bf0a8 | ||
|
|
3e9a4f2c1a | ||
|
|
960543d14d | ||
|
|
299674f53b | ||
|
|
306b78b526 | ||
|
|
f369382a54 | ||
|
|
ac67d88e74 | ||
|
|
1f4f70009c | ||
|
|
838d918451 | ||
|
|
8e6c73d2bc | ||
|
|
aeb599867c | ||
|
|
2fe93d4e64 | ||
|
|
3e789e0642 | ||
|
|
a762f206a1 | ||
|
|
c0b13a1f09 | ||
|
|
070ddff1b4 | ||
|
|
9d0770e360 | ||
|
|
088cb2a30e | ||
|
|
c2be84a367 | ||
|
|
4f61dd7bb3 | ||
|
|
30fb6fd8e2 | ||
|
|
4bfb5d9f34 | ||
|
|
023f29491d | ||
|
|
5166fe2b41 | ||
|
|
71aa41d55e | ||
|
|
f6fd50f449 | ||
|
|
81e2f7c288 | ||
|
|
895d5e50de | ||
|
|
6cd1b1ceaa | ||
|
|
82ae9409f1 | ||
|
|
764b879a77 | ||
|
|
d3e71b6a7e | ||
|
|
80d23d1c95 | ||
|
|
531e89346e | ||
|
|
f220cb52bb | ||
|
|
58ff42f813 | ||
|
|
6b82b03bf1 | ||
|
|
005843ef2d | ||
|
|
10917644ab | ||
|
|
9fa3ade832 | ||
|
|
e10135271d | ||
|
|
c1b482e0c0 | ||
|
|
a3d7760a09 | ||
|
|
57db7c1efc | ||
|
|
586c9b6db6 | ||
|
|
58a86b67ee | ||
|
|
43ebf50b61 | ||
|
|
5071cf4752 | ||
|
|
00e2e79fc5 | ||
|
|
8d37444755 | ||
|
|
20d81bee00 | ||
|
|
ca387d7b26 | ||
|
|
a0580f6861 | ||
|
|
f739db1e42 | ||
|
|
d5bfe7cfbd | ||
|
|
db85e088bf | ||
|
|
d053901b32 | ||
|
|
2957c7d6a9 | ||
|
|
a69d52a389 | ||
|
|
7b5dbbd7ed | ||
|
|
6d0fb6ca80 | ||
|
|
b4f7344ae4 | ||
|
|
a5cecb7354 | ||
|
|
406a15c5bd | ||
|
|
ed0408de95 | ||
|
|
b5bba1fd11 | ||
|
|
9065bad2bb | ||
|
|
289034fd4f | ||
|
|
f052f6696f | ||
|
|
267ca178ed | ||
|
|
c01c16c7bc | ||
|
|
2ac069082d | ||
|
|
f00ea09b6e | ||
|
|
b9259ce6ca | ||
|
|
0490638657 | ||
|
|
467b6ba9da | ||
|
|
423a42d25b | ||
|
|
b496dc488c | ||
|
|
9750723035 | ||
|
|
a4f147b547 | ||
|
|
0827fedb74 | ||
|
|
898b908c1b | ||
|
|
f21b70a51e | ||
|
|
ba7524b754 | ||
|
|
eb068a8d53 | ||
|
|
14fbc3a5b4 | ||
|
|
d2fa8902f9 | ||
|
|
53e5728ad2 | ||
|
|
dd7de055f6 | ||
|
|
3cd7249750 | ||
|
|
07165ce68d | ||
|
|
2c0fe71f14 | ||
|
|
c74a53b301 | ||
|
|
3663f723e7 | ||
|
|
233da1508b | ||
|
|
ad34e9aeb8 | ||
|
|
d29dc9d036 | ||
|
|
d71030fdef | ||
|
|
bb5ca475d3 | ||
|
|
01c7a8fcdb | ||
|
|
f6c46878c6 | ||
|
|
acda6bba74 | ||
|
|
c8ee066608 | ||
|
|
ca9fffaa71 | ||
|
|
e00b7c9be9 | ||
|
|
ee1a2b1810 | ||
|
|
14266a99b3 | ||
|
|
7b105107cc | ||
|
|
bb75242a4f | ||
|
|
39ccc30680 | ||
|
|
41651075e6 | ||
|
|
e3af463e65 | ||
|
|
5cbb8b1fa3 | ||
|
|
2a7b74a0e1 | ||
|
|
21fc493322 | ||
|
|
ea85eae4ce | ||
|
|
35ee97c145 | ||
|
|
097318cf9e | ||
|
|
31d4f3d1b3 | ||
|
|
83b3235a6b | ||
|
|
e47fc64c33 | ||
|
|
151a49f1ec | ||
|
|
b78644f7e2 | ||
|
|
d744e36dde | ||
|
|
90f4bd5120 | ||
|
|
b436fcf749 | ||
|
|
ffcc229c78 | ||
|
|
ffe724b32d | ||
|
|
154d32cd31 | ||
|
|
7f056277ae | ||
|
|
b19cbf54e0 | ||
|
|
aef0e363d8 | ||
|
|
6324034259 | ||
|
|
5a1bada10c | ||
|
|
ef90305bd7 | ||
|
|
fe06a29ad2 | ||
|
|
f6ae9822f3 | ||
|
|
eb3e4916a8 | ||
|
|
5a5535ea98 | ||
|
|
4f8d0b1e7a | ||
|
|
b1d64eac88 | ||
|
|
04673a4804 | ||
|
|
4d2f9d038a | ||
|
|
e4201c1a4d | ||
|
|
030c001371 | ||
|
|
bd351f770b | ||
|
|
63093cca3b | ||
|
|
5c42e8e5bc | ||
|
|
153934b311 | ||
|
|
4b1ead1a36 | ||
|
|
ddafd21706 | ||
|
|
6059a944bf | ||
|
|
1cc26c3902 | ||
|
|
00ca3d856b | ||
|
|
9af8ab92c9 | ||
|
|
46fcfcc321 | ||
|
|
cf40462531 | ||
|
|
2dbf9671d9 | ||
|
|
e42a687c9b | ||
|
|
72c154b048 | ||
|
|
b0921513c1 | ||
|
|
33a76c61ca | ||
|
|
c0254d0b92 | ||
|
|
99d05fe4f9 | ||
|
|
f8ba6a0c5f | ||
|
|
3a58ed63e9 | ||
|
|
8aac801af2 | ||
|
|
1cc16a7ed4 | ||
|
|
7fe04d9651 | ||
|
|
01a9dc4d2d | ||
|
|
1456462ca8 | ||
|
|
77415dc0e0 | ||
|
|
696b1970fc | ||
|
|
0e6baae366 | ||
|
|
437e9ee43b | ||
|
|
6546f47bb2 | ||
|
|
88c866057f | ||
|
|
3521dd41cd | ||
|
|
dff3e6aaca | ||
|
|
4b04e6c8dc | ||
|
|
37672ea632 | ||
|
|
31f35ad902 | ||
|
|
0b7b34f336 | ||
|
|
fca290f8f5 | ||
|
|
9da3b77f7d | ||
|
|
7b796f4a5f | ||
|
|
0ce81169a6 | ||
|
|
d1c2abf7e3 | ||
|
|
010872ef35 | ||
|
|
f536033087 | ||
|
|
9083888686 | ||
|
|
cf2d8531a3 | ||
|
|
48b9e2f97b | ||
|
|
57ac20519a |
@@ -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?
|
||||||
22
.gitignore
vendored
22
.gitignore
vendored
@@ -10,15 +10,29 @@ msbuild.binlog
|
|||||||
*.zip
|
*.zip
|
||||||
|
|
||||||
*.idea
|
*.idea
|
||||||
|
_ReSharper.Caches
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
Oqtane.Server/appsettings.json
|
Oqtane.Server/appsettings.json
|
||||||
Oqtane.Server/Data/*.mdf
|
Oqtane.Server/Data
|
||||||
Oqtane.Server/Data/*.ldf
|
|
||||||
Oqtane.Server/Data/*.db
|
|
||||||
|
|
||||||
/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/_content/*
|
||||||
|
!Oqtane.Server/wwwroot/_content/Placeholder.txt
|
||||||
|
|
||||||
|
Oqtane.Server/wwwroot/Modules/*
|
||||||
|
!Oqtane.Server/wwwroot/Modules/Oqtane.Modules.*
|
||||||
|
!Oqtane.Server/wwwroot/Modules/Templates
|
||||||
|
Oqtane.Server/wwwroot/Modules/Templates/*
|
||||||
|
!Oqtane.Server/wwwroot/Modules/Templates/External
|
||||||
|
|
||||||
|
Oqtane.Server/wwwroot/Themes/*
|
||||||
|
!Oqtane.Server/wwwroot/Themes/Oqtane.Themes.*
|
||||||
|
!Oqtane.Server/wwwroot/Themes/Templates
|
||||||
|
Oqtane.Server/wwwroot/Themes/Templates/*
|
||||||
|
Oqtane.Server/wwwroot/Themes/Templates/External
|
||||||
|
|||||||
24
CONTRIBUTING.md
Normal file
24
CONTRIBUTING.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Contributing to Oqtane
|
||||||
|
|
||||||
|
## How to Contribute
|
||||||
|
|
||||||
|
We track all of our issues on Github. If you want to contribute, everything starts with an issue. If you don't have an issue yet, you can add one. Then a core contributor will tag it as either an enhancement [ENH] or a bug [BUG]. Tagged issues are open for contribution.
|
||||||
|
|
||||||
|
## Use GitHub-flow process
|
||||||
|
- Make a comment on the issue that you intend to work on it and read all the comments to gain a full understanding.
|
||||||
|
- Fork the repository
|
||||||
|
- Create a new branch and update your comment on the issue with a llink to the branch
|
||||||
|
- Make your changes and commit them
|
||||||
|
- Push to the branch
|
||||||
|
- Create a pull request
|
||||||
|
|
||||||
|
## Reporting Bugs
|
||||||
|
|
||||||
|
- Check if the issue has already been reported.
|
||||||
|
- Open a new issue if it hasn’t been reported.
|
||||||
|
|
||||||
|
## Requesting Features
|
||||||
|
|
||||||
|
- Use the feature request template in the Issues tab.
|
||||||
|
|
||||||
|
Thank you for contributing!
|
||||||
27
Dockerfile
Normal file
27
Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Build
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||||
|
|
||||||
|
WORKDIR /source
|
||||||
|
|
||||||
|
COPY --link . .
|
||||||
|
|
||||||
|
RUN dotnet restore /source/Oqtane.sln
|
||||||
|
|
||||||
|
RUN dotnet build "/source/Oqtane.sln" -c Release -o /source/build/
|
||||||
|
|
||||||
|
# Publish
|
||||||
|
FROM build AS publish
|
||||||
|
|
||||||
|
RUN dotnet publish "Oqtane.Server/Oqtane.Server.csproj" -c Release -o /source/publish/
|
||||||
|
|
||||||
|
# Deploy
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS deploy
|
||||||
|
|
||||||
|
WORKDIR /codefiles
|
||||||
|
|
||||||
|
COPY --from=publish /source/publish/ /codefiles/
|
||||||
|
|
||||||
|
COPY entrypoint.sh .
|
||||||
|
RUN chmod +x entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2018-2020 .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,61 +0,0 @@
|
|||||||
@inject IInstallationService InstallationService
|
|
||||||
@inject IJSRuntime JSRuntime
|
|
||||||
@inject SiteState SiteState
|
|
||||||
|
|
||||||
@if (_initialized)
|
|
||||||
{
|
|
||||||
@if (!_installation.Success)
|
|
||||||
{
|
|
||||||
<Installer />
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@if (string.IsNullOrEmpty(_installation.Message))
|
|
||||||
{
|
|
||||||
<CascadingAuthenticationState>
|
|
||||||
<CascadingValue Value="@PageState">
|
|
||||||
<SiteRouter OnStateChange="@ChangeState" />
|
|
||||||
</CascadingValue>
|
|
||||||
</CascadingAuthenticationState>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="app-alert">
|
|
||||||
@_installation.Message
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private bool _initialized = false;
|
|
||||||
private Installation _installation = new Installation { Success = false, Message = "" };
|
|
||||||
|
|
||||||
private PageState PageState { get; set; }
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender && !_initialized)
|
|
||||||
{
|
|
||||||
var interop = new Interop(JSRuntime);
|
|
||||||
SiteState.AntiForgeryToken = await interop.GetElementByName(Constants.RequestVerificationToken);
|
|
||||||
_installation = await InstallationService.IsInstalled();
|
|
||||||
if (_installation.Alias != null)
|
|
||||||
{
|
|
||||||
SiteState.Alias = _installation.Alias;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_installation.Message = "Site Not Configured Correctly - No Matching Alias Exists For Host Name";
|
|
||||||
}
|
|
||||||
_initialized = true;
|
|
||||||
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>();
|
||||||
@@ -46,7 +48,17 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddScoped<ILocalizationService, LocalizationService>();
|
services.AddScoped<ILocalizationService, LocalizationService>();
|
||||||
services.AddScoped<ILanguageService, LanguageService>();
|
services.AddScoped<ILanguageService, LanguageService>();
|
||||||
services.AddScoped<IDatabaseService, DatabaseService>();
|
services.AddScoped<IDatabaseService, DatabaseService>();
|
||||||
|
services.AddScoped<IUrlMappingService, UrlMappingService>();
|
||||||
|
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,41 +1,30 @@
|
|||||||
@namespace Oqtane.Installer.Controls
|
@namespace Oqtane.Installer.Controls
|
||||||
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
||||||
@inject IStringLocalizer<Installer> Localizer
|
|
||||||
|
|
||||||
@{
|
<div class="row mb-1 align-items-center">
|
||||||
foreach (var field in _connectionStringFields)
|
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
|
||||||
{
|
<div class="col-sm-9">
|
||||||
var fieldId = field.Name.ToLowerInvariant();
|
<input id="server" type="text" class="form-control" @bind="@_server" />
|
||||||
field.Value = field.Value.Replace("{{Date}}", DateTime.UtcNow.ToString("yyyyMMddHHmm"));
|
</div>
|
||||||
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="database" HelpText="Enter the name of the database" ResourceKey="Database">Database:</Label>
|
||||||
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="database" type="text" class="form-control" @bind="@_database" />
|
||||||
<td>
|
</div>
|
||||||
<input id="@fieldId" type="text" class="form-control" @bind="@field.Value" />
|
</div>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private readonly List<ConnectionStringField> _connectionStringFields = new()
|
private string _server = "(LocalDb)\\MSSQLLocalDB";
|
||||||
{
|
private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
|
||||||
new() {Name = "Server", FriendlyName = "Server", Value = "(LocalDb)\\MSSQLLocalDB", HelpText="Enter the database server"},
|
|
||||||
new() {Name = "Database", FriendlyName = "Database", Value = "Oqtane-{{Date}}", HelpText="Enter the name of the database"}
|
|
||||||
};
|
|
||||||
|
|
||||||
public string GetConnectionString()
|
public string GetConnectionString()
|
||||||
{
|
{
|
||||||
var connectionString = String.Empty;
|
var connectionString = String.Empty;
|
||||||
|
|
||||||
var server = _connectionStringFields[0].Value;
|
if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database))
|
||||||
var database = _connectionStringFields[1].Value;
|
|
||||||
|
|
||||||
if (!String.IsNullOrEmpty(server) && !String.IsNullOrEmpty(database))
|
|
||||||
{
|
{
|
||||||
connectionString = $"Data Source={server};AttachDbFilename=|DataDirectory|\\{database}.mdf;Initial Catalog={database};Integrated Security=SSPI;";
|
connectionString = $"Data Source={_server};AttachDbFilename=|DataDirectory|\\{_database}.mdf;Initial Catalog={_database};Integrated Security=SSPI;Encrypt=false;";
|
||||||
}
|
}
|
||||||
|
|
||||||
return connectionString;
|
return connectionString;
|
||||||
|
|||||||
@@ -1,54 +1,83 @@
|
|||||||
@namespace Oqtane.Installer.Controls
|
@namespace Oqtane.Installer.Controls
|
||||||
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
||||||
@inject IStringLocalizer<Installer> Localizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@{
|
<div class="row mb-1 align-items-center">
|
||||||
foreach (var field in _connectionStringFields)
|
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
|
||||||
{
|
<div class="col-sm-9">
|
||||||
var fieldId = field.Name.ToLowerInvariant();
|
<input id="server" type="text" class="form-control" @bind="@_server" />
|
||||||
var fieldType = (field.Name == "Pwd") ? "password" : "text";
|
</div>
|
||||||
field.Value = field.Value.Replace("{{Date}}", DateTime.UtcNow.ToString("yyyyMMddHHmm"));
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="port" HelpText="Enter the port used to connect to the server" ResourceKey="Port">Port:</Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label>
|
<input id="port" type="text" class="form-control" @bind="@_port" />
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<input id="@fieldId" type="@fieldType" class="form-control" @bind="@field.Value" />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="database" HelpText="Enter the name of the database" ResourceKey="Database">Database:</Label>
|
||||||
</tr>
|
<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="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>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private readonly List<ConnectionStringField> _connectionStringFields = new()
|
private string _server = "127.0.0.1";
|
||||||
|
private string _port = "3306";
|
||||||
|
private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
|
||||||
|
private string _uid = String.Empty;
|
||||||
|
private string _pwd = String.Empty;
|
||||||
|
private string _passwordType = "password";
|
||||||
|
private string _togglePassword = string.Empty;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
new() {Name = "Server", FriendlyName = "Server", Value = "127.0.0.1", HelpText="Enter the database server"},
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
new() {Name = "Port", FriendlyName = "Port", Value = "3306", HelpText="Enter the port used to connect to the server"},
|
}
|
||||||
new() {Name = "Database", FriendlyName = "Database", Value = "Oqtane-{{Date}}", HelpText="Enter the name of the database"},
|
|
||||||
new() {Name = "Uid", FriendlyName = "User Id", Value = "", HelpText="Enter the username to use for the database"},
|
|
||||||
new() {Name = "Pwd", FriendlyName = "Password", Value = "", HelpText="Enter the password to use for the database"}
|
|
||||||
};
|
|
||||||
|
|
||||||
public string GetConnectionString()
|
public string GetConnectionString()
|
||||||
{
|
{
|
||||||
var connectionString = String.Empty;
|
var connectionString = String.Empty;
|
||||||
|
|
||||||
var server = _connectionStringFields[0].Value;
|
if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database) && !String.IsNullOrEmpty(_uid) && !String.IsNullOrEmpty(_pwd))
|
||||||
var port = _connectionStringFields[1].Value;
|
|
||||||
var database = _connectionStringFields[2].Value;
|
|
||||||
var userId = _connectionStringFields[3].Value;
|
|
||||||
var password = _connectionStringFields[4].Value;
|
|
||||||
|
|
||||||
if (!String.IsNullOrEmpty(server) && !String.IsNullOrEmpty(database) && !String.IsNullOrEmpty(userId) && !String.IsNullOrEmpty(password))
|
|
||||||
{
|
{
|
||||||
connectionString = $"Server={server};Database={database};Uid={userId};Pwd={password};";
|
connectionString = $"Server={_server};Database={_database};Uid={_uid};Pwd={_pwd};";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!String.IsNullOrEmpty(port))
|
if (!String.IsNullOrEmpty(_port))
|
||||||
{
|
{
|
||||||
connectionString += $"Port={port};";
|
connectionString += $"Port={_port};";
|
||||||
}
|
}
|
||||||
|
|
||||||
return connectionString;
|
return connectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordType == "password")
|
||||||
|
{
|
||||||
|
_passwordType = "text";
|
||||||
|
_togglePassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordType = "password";
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,89 +1,87 @@
|
|||||||
@namespace Oqtane.Installer.Controls
|
@namespace Oqtane.Installer.Controls
|
||||||
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
||||||
@inject IStringLocalizer<Installer> Localizer
|
@inject IStringLocalizer<PostgreSQLConfig> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@{
|
<div class="row mb-1 align-items-center">
|
||||||
foreach (var field in _connectionStringFields)
|
<Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
|
||||||
{
|
<div class="col-sm-9">
|
||||||
var fieldId = field.Name.ToLowerInvariant();
|
<input id="server" type="text" class="form-control" @bind="@_server" />
|
||||||
if (field.Name != "IntegratedSecurity")
|
</div>
|
||||||
{
|
</div>
|
||||||
var isVisible = "";
|
<div class="row mb-1 align-items-center">
|
||||||
var fieldType = (field.Name == "Pwd") ? "password" : "text";
|
<Label Class="col-sm-3" For="port" HelpText="Enter the port used to connect to the server" ResourceKey="Port">Port:</Label>
|
||||||
if ((field.Name == "Uid" || field.Name == "Pwd"))
|
<div class="col-sm-9">
|
||||||
{
|
<input id="port" type="text" class="form-control" @bind="@_port" />
|
||||||
var intSecurityField = _connectionStringFields.Single(f => f.Name == "IntegratedSecurity");
|
</div>
|
||||||
if (intSecurityField != null)
|
</div>
|
||||||
{
|
<div class="row mb-1 align-items-center">
|
||||||
isVisible = (Convert.ToBoolean(intSecurityField.Value)) ? "display: none;" : "";
|
<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>
|
||||||
field.Value = field.Value.Replace("{{Date}}", DateTime.UtcNow.ToString("yyyyMMddHHmm"));
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
<tr style="@isVisible">
|
<Label Class="col-sm-3" For="security" HelpText="Select your security method" ResourceKey="Security">Security:</Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label>
|
<select id="security" class="form-select custom-select" @bind="@_security">
|
||||||
</td>
|
<option value="integrated" selected>@Localizer["Integrated"]</option>
|
||||||
<td>
|
<option value="custom">@Localizer["Custom"]</option>
|
||||||
<input id="@fieldId" type="@fieldType" class="form-control" @bind="@field.Value" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="@fieldId" class="custom-select" @bind="@field.Value">
|
|
||||||
<option value="true" selected>@SharedLocalizer["True"]</option>
|
|
||||||
<option value="false">@SharedLocalizer["False"]</option>
|
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</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>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private readonly List<ConnectionStringField> _connectionStringFields = new()
|
private string _server = "127.0.0.1";
|
||||||
|
private string _port = "5432";
|
||||||
|
private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
|
||||||
|
private string _security = "integrated";
|
||||||
|
private string _uid = String.Empty;
|
||||||
|
private string _pwd = String.Empty;
|
||||||
|
private string _passwordType = "password";
|
||||||
|
private string _togglePassword = string.Empty;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
new() { Name = "Server", FriendlyName = "Server", Value = "127.0.0.1", HelpText = "Enter the database server" },
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
new() { Name = "Port", FriendlyName = "Port", Value = "5432", HelpText = "Enter the port used to connect to the server" },
|
}
|
||||||
new() { Name = "Database", FriendlyName = "Database", Value = "Oqtane-{{Date}}", HelpText = "Enter the name of the database" },
|
|
||||||
new() { Name = "IntegratedSecurity", FriendlyName = "Integrated Security", Value = "true", HelpText = "Select if you want integrated security or not" },
|
|
||||||
new() { Name = "Uid", FriendlyName = "User Id", Value = "", HelpText = "Enter the username to use for the database" },
|
|
||||||
new() { Name = "Pwd", FriendlyName = "Password", Value = "", HelpText = "Enter the password to use for the database" }
|
|
||||||
};
|
|
||||||
|
|
||||||
public string GetConnectionString()
|
public string GetConnectionString()
|
||||||
{
|
{
|
||||||
var connectionString = String.Empty;
|
var connectionString = String.Empty;
|
||||||
|
|
||||||
var server = _connectionStringFields[0].Value;
|
if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database) && !String.IsNullOrEmpty(_port))
|
||||||
var port = _connectionStringFields[1].Value;
|
|
||||||
var database = _connectionStringFields[2].Value;
|
|
||||||
var integratedSecurity = Boolean.Parse(_connectionStringFields[3].Value);
|
|
||||||
var userId = _connectionStringFields[4].Value;
|
|
||||||
var password = _connectionStringFields[5].Value;
|
|
||||||
|
|
||||||
if (!String.IsNullOrEmpty(server) && !String.IsNullOrEmpty(database) && !String.IsNullOrEmpty(port))
|
|
||||||
{
|
{
|
||||||
connectionString = $"Server={server};Port={port};Database={database};";
|
connectionString = $"Server={_server};Port={_port};Database={_database};";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (integratedSecurity)
|
if (_security == "integrated")
|
||||||
{
|
{
|
||||||
connectionString += "Integrated Security=true;";
|
connectionString += "Integrated Security=true;";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!String.IsNullOrEmpty(userId) && !String.IsNullOrEmpty(password))
|
if (!String.IsNullOrEmpty(_uid) && !String.IsNullOrEmpty(_pwd))
|
||||||
{
|
{
|
||||||
connectionString += $"User ID={userId};Password={password};";
|
connectionString += $"User ID={_uid};Password={_pwd};";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -93,4 +91,18 @@
|
|||||||
|
|
||||||
return connectionString;
|
return connectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordType == "password")
|
||||||
|
{
|
||||||
|
_passwordType = "text";
|
||||||
|
_togglePassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordType = "password";
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,95 +1,119 @@
|
|||||||
@namespace Oqtane.Installer.Controls
|
@namespace Oqtane.Installer.Controls
|
||||||
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
||||||
@inject IStringLocalizer<Installer> Localizer
|
@inject IStringLocalizer<SqlServerConfig> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@{
|
<div class="row mb-1 align-items-center">
|
||||||
foreach (var field in _connectionStringFields)
|
<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">
|
||||||
var fieldId = field.Name.ToLowerInvariant();
|
<input id="server" type="text" class="form-control" @bind="@_server" />
|
||||||
if (field.Name != "IntegratedSecurity")
|
</div>
|
||||||
{
|
</div>
|
||||||
var isVisible = "";
|
<div class="row mb-1 align-items-center">
|
||||||
var fieldType = (field.Name == "Pwd") ? "password" : "text";
|
<Label Class="col-sm-3" For="database" HelpText="Enter the name of the database" ResourceKey="Database">Database:</Label>
|
||||||
if ((field.Name == "Uid" || field.Name == "Pwd"))
|
<div class="col-sm-9">
|
||||||
{
|
<input id="database" type="text" class="form-control" @bind="@_database" />
|
||||||
var intSecurityField = _connectionStringFields.Single(f => f.Name == "IntegratedSecurity");
|
</div>
|
||||||
if (intSecurityField != null)
|
</div>
|
||||||
{
|
<div class="row mb-1 align-items-center">
|
||||||
isVisible = (Convert.ToBoolean(intSecurityField.Value)) ? "display: none;" : "";
|
<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>
|
||||||
field.Value = field.Value.Replace("{{Date}}", DateTime.UtcNow.ToString("yyyyMMddHHmm"));
|
<option value="custom">@Localizer["Custom"]</option>
|
||||||
|
</select>
|
||||||
<tr style="@isVisible">
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label>
|
@if (_security == "custom")
|
||||||
</td>
|
{
|
||||||
<td>
|
<div class="row mb-1 align-items-center">
|
||||||
<input id="@fieldId" type="@fieldType" class="form-control" @bind="@field.Value" />
|
<Label Class="col-sm-3" For="uid" HelpText="Enter the username to use for the database" ResourceKey="Uid">User Id:</Label>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
</tr>
|
<input id="uid" type="text" class="form-control" @bind="@_uid" />
|
||||||
}
|
</div>
|
||||||
else
|
</div>
|
||||||
{
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label>
|
<div class="input-group">
|
||||||
</td>
|
<input id="pwd" type="@_passwordType" class="form-control" @bind="@_pwd" autocomplete="new-password" />
|
||||||
<td>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
||||||
<select id="@fieldId" class="custom-select" @bind="@field.Value">
|
</div>
|
||||||
<option value="true" selected>@SharedLocalizer["True"]</option>
|
</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>
|
<option value="false">@SharedLocalizer["False"]</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</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 {
|
@code {
|
||||||
private readonly List<ConnectionStringField> _connectionStringFields = new()
|
private string _server = String.Empty;
|
||||||
|
private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
|
||||||
|
private string _security = "integrated";
|
||||||
|
private string _uid = String.Empty;
|
||||||
|
private string _pwd = String.Empty;
|
||||||
|
private string _passwordType = "password";
|
||||||
|
private string _togglePassword = string.Empty;
|
||||||
|
private string _encryption = "false";
|
||||||
|
private string _trustservercertificate = "false";
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
new() { Name = "Server", FriendlyName = "Server", Value = ".", HelpText = "Enter the database server" },
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
new() { Name = "Database", FriendlyName = "Database", Value = "Oqtane-{{Date}}", HelpText = "Enter the name of the database" },
|
}
|
||||||
new() { Name = "IntegratedSecurity", FriendlyName = "Integrated Security", Value = "true", HelpText = "Select if you want integrated security or not" },
|
|
||||||
new() { Name = "Uid", FriendlyName = "User Id", Value = "", HelpText = "Enter the username to use for the database" },
|
|
||||||
new() { Name = "Pwd", FriendlyName = "Password", Value = "", HelpText = "Enter the password to use for the database" }
|
|
||||||
};
|
|
||||||
|
|
||||||
public string GetConnectionString()
|
public string GetConnectionString()
|
||||||
{
|
{
|
||||||
var connectionString = String.Empty;
|
var connectionString = String.Empty;
|
||||||
|
|
||||||
var server = _connectionStringFields[0].Value;
|
if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database))
|
||||||
var database = _connectionStringFields[1].Value;
|
|
||||||
var integratedSecurity = Boolean.Parse(_connectionStringFields[2].Value);
|
|
||||||
var userId = _connectionStringFields[3].Value;
|
|
||||||
var password = _connectionStringFields[4].Value;
|
|
||||||
|
|
||||||
if (!String.IsNullOrEmpty(server) && !String.IsNullOrEmpty(database))
|
|
||||||
{
|
{
|
||||||
connectionString = $"Data Source={server};Initial Catalog={database};";
|
connectionString = $"Data Source={_server};Initial Catalog={_database};";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (integratedSecurity)
|
if (_security == "integrated")
|
||||||
{
|
{
|
||||||
connectionString += "Integrated Security=SSPI;";
|
connectionString += "Integrated Security=SSPI;";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!String.IsNullOrEmpty(userId) && !String.IsNullOrEmpty(password))
|
connectionString += $"User ID={_uid};Password={_pwd};";
|
||||||
{
|
|
||||||
connectionString += $"User ID={userId};Password={password};";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
connectionString = String.Empty;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
connectionString += $"Encrypt={_encryption};";
|
||||||
|
connectionString += $"TrustServerCertificate={_trustservercertificate};";
|
||||||
|
|
||||||
return connectionString;
|
return connectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordType == "password")
|
||||||
|
{
|
||||||
|
_passwordType = "text";
|
||||||
|
_togglePassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordType = "password";
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,39 +1,23 @@
|
|||||||
@namespace Oqtane.Installer.Controls
|
@namespace Oqtane.Installer.Controls
|
||||||
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
@implements Oqtane.Interfaces.IDatabaseConfigControl
|
||||||
@inject IStringLocalizer<Installer> Localizer
|
|
||||||
|
|
||||||
@{
|
<div class="row mb-1 align-items-center">
|
||||||
foreach (var field in _connectionStringFields)
|
<Label Class="col-sm-3" For="server" HelpText="Enter the file name to use for the database" ResourceKey="Server">File Name:</Label>
|
||||||
{
|
<div class="col-sm-9">
|
||||||
var fieldId = field.Name.ToLowerInvariant();
|
<input id="server" type="text" class="form-control" @bind="@_server" />
|
||||||
field.Value = field.Value.Replace("{{Date}}", DateTime.UtcNow.ToString("yyyyMMddHHmm"));
|
</div>
|
||||||
|
</div>
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="@fieldId" type="text" class="form-control" @bind="@field.Value" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private readonly List<ConnectionStringField> _connectionStringFields = new()
|
private string _server = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm") + ".db";
|
||||||
{
|
|
||||||
new() {Name = "Server", FriendlyName = "File Name", Value = "Oqtane-{{Date}}.db", HelpText="Enter the file name to use for the database"}
|
|
||||||
};
|
|
||||||
|
|
||||||
public string GetConnectionString()
|
public string GetConnectionString()
|
||||||
{
|
{
|
||||||
var connectionstring = String.Empty;
|
var connectionstring = String.Empty;
|
||||||
|
|
||||||
var server = _connectionStringFields[0].Value;
|
if (!String.IsNullOrEmpty(_server))
|
||||||
|
|
||||||
if (!String.IsNullOrEmpty(server))
|
|
||||||
{
|
{
|
||||||
connectionstring = $"Data Source={server};";
|
connectionstring = $"Data Source={_server};";
|
||||||
}
|
}
|
||||||
|
|
||||||
return connectionstring;
|
return connectionstring;
|
||||||
|
|||||||
@@ -5,100 +5,133 @@
|
|||||||
@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" />
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<h2>@Localizer["DatabaseConfig"]</h2><br />
|
<h2>@Localizer["DatabaseConfig"]</h2><br />
|
||||||
<table class="form-group" cellpadding="4" cellspacing="4" style="margin: auto;">
|
<div class="container">
|
||||||
<tbody>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="databasetype" HelpText="Select the type of database you wish to use" ResourceKey="DatabaseType">Database:</Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="databasetype" HelpText="Select the type of database you wish to create" ResourceKey="DatabaseType">Database Type:</Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="databasetype" class="custom-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))">
|
|
||||||
@if (_databases != null)
|
@if (_databases != null)
|
||||||
{
|
{
|
||||||
foreach (var database in _databases)
|
<div class="input-group">
|
||||||
{
|
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
|
||||||
if (database.IsDefault)
|
@foreach (var database in _databases)
|
||||||
{
|
|
||||||
<option value="@database.Name" selected>@Localizer[@database.Name]</option>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
<option value="@database.Name">@Localizer[@database.Name]</option>
|
<option value="@database.Name">@Localizer[@database.Name]</option>
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
</select>
|
</select>
|
||||||
</td>
|
@if (!_showConnectionString)
|
||||||
</tr>
|
{
|
||||||
@{
|
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (!_showConnectionString)
|
||||||
|
{
|
||||||
if (_databaseConfigType != null)
|
if (_databaseConfigType != null)
|
||||||
{
|
{
|
||||||
@DatabaseConfigComponent;
|
@DatabaseConfigComponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</tbody>
|
else
|
||||||
</table>
|
{
|
||||||
|
<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 class="col text-center">
|
<div class="col text-center">
|
||||||
<h2>@Localizer["ApplicationAdmin"]</h2><br />
|
<h2>@Localizer["ApplicationAdmin"]</h2><br />
|
||||||
<table class="form-group" cellpadding="4" cellspacing="4" style="margin: auto;">
|
<div class="container">
|
||||||
<tbody>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="username" HelpText="Provide a username for the primary user account" ResourceKey="Username">Username:</Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="username" HelpText="The username of the host user account ( this is not customizable )" ResourceKey="Username">Username:</Label>
|
<input id="username" type="text" class="form-control" maxlength="256" @bind="@_hostUsername" />
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<input id="username" type="text" class="form-control" @bind="@_hostUsername" readonly />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
|
||||||
</tr>
|
<div class="col-sm-9">
|
||||||
<tr>
|
<div class="input-group">
|
||||||
<td>
|
<input id="password" type="@_passwordType" class="form-control" maxlength="256" @bind="@_hostPassword" autocomplete="new-password" />
|
||||||
<Label For="password" HelpText="Provide the password for the host user account" ResourceKey="Password">Password:</Label>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<input id="password" type="password" class="form-control" @bind="@_hostPassword" />
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
</tr>
|
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
|
||||||
<tr>
|
<div class="col-sm-9">
|
||||||
<td>
|
<div class="input-group">
|
||||||
<Label For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
|
<input id="confirm" type="@_confirmPasswordType" class="form-control" maxlength="256" @bind="@_confirmPassword" autocomplete="new-password" />
|
||||||
</td>
|
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword" tabindex="-1">@_toggleConfirmPassword</button>
|
||||||
<td>
|
</div>
|
||||||
<input id="confirm" type="password" class="form-control" @bind="@_confirmPassword" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
|
<input type="text" class="form-control" maxlength="256" @bind="@_hostEmail" />
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<input type="text" class="form-control" @bind="@_hostEmail" />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="name" HelpText="Provide the full name of the host user" ResourceKey="Name">Full Name:</Label>
|
||||||
</tr>
|
<div class="col-sm-9">
|
||||||
</tbody>
|
<input type="text" class="form-control" maxlength="50" @bind="@_hostName" />
|
||||||
</table>
|
</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>
|
</div>
|
||||||
<hr class="app-rule" />
|
<hr class="app-rule" />
|
||||||
<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>
|
||||||
@@ -111,23 +144,49 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Database> _databases;
|
private List<Database> _databases;
|
||||||
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 string _hostUsername = UserNames.Host;
|
private string _hostUsername = string.Empty;
|
||||||
private string _hostPassword = string.Empty;
|
private string _hostPassword = string.Empty;
|
||||||
|
private string _passwordType = "password";
|
||||||
|
private string _confirmPasswordType = "password";
|
||||||
|
private string _togglePassword = string.Empty;
|
||||||
|
private string _toggleConfirmPassword = string.Empty;
|
||||||
private string _confirmPassword = string.Empty;
|
private string _confirmPassword = string.Empty;
|
||||||
private string _hostEmail = string.Empty;
|
private string _hostEmail = string.Empty;
|
||||||
|
private string _hostName = string.Empty;
|
||||||
|
private List<SiteTemplate> _templates;
|
||||||
|
private string _template = Constants.DefaultSiteTemplate;
|
||||||
private bool _register = true;
|
private bool _register = true;
|
||||||
private string _message = string.Empty;
|
private string _message = string.Empty;
|
||||||
private string _loadingDisplay = "display: none;";
|
private string _loadingDisplay = "display: none;";
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
// include CSS
|
||||||
|
var content = $"<link rel=\"stylesheet\" href=\"{Constants.BootstrapStylesheetUrl}\" integrity=\"{Constants.BootstrapStylesheetIntegrity}\" crossorigin=\"anonymous\" type=\"text/css\"/>";
|
||||||
|
SiteState.AppendHeadContent(content);
|
||||||
|
|
||||||
|
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||||
|
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
|
||||||
|
|
||||||
_databases = await DatabaseService.GetDatabasesAsync();
|
_databases = await DatabaseService.GetDatabasesAsync();
|
||||||
|
if (_databases.Exists(item => item.IsDefault))
|
||||||
|
{
|
||||||
|
_databaseName = _databases.Find(item => item.IsDefault).Name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_databaseName = "LocalDB";
|
||||||
|
}
|
||||||
LoadDatabaseConfigComponent();
|
LoadDatabaseConfigComponent();
|
||||||
|
|
||||||
|
_templates = await SiteTemplateService.GetSiteTemplatesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DatabaseChanged(ChangeEventArgs eventArgs)
|
private void DatabaseChanged(ChangeEventArgs eventArgs)
|
||||||
@@ -135,7 +194,7 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_databaseName = (string)eventArgs.Value;
|
_databaseName = (string)eventArgs.Value;
|
||||||
|
_showConnectionString = false;
|
||||||
LoadDatabaseConfigComponent();
|
LoadDatabaseConfigComponent();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -163,20 +222,31 @@
|
|||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
|
// include JavaScript
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
await interop.IncludeLink("app-stylesheet", "stylesheet", "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css", "text/css", "sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T", "anonymous", "");
|
await interop.IncludeScript("", Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous", "", "head");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Install()
|
private async Task Install()
|
||||||
{
|
{
|
||||||
var connectionString = String.Empty;
|
var connectionString = String.Empty;
|
||||||
|
if (_showConnectionString)
|
||||||
|
{
|
||||||
|
connectionString = _connectionString;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||||
{
|
{
|
||||||
connectionString = databaseConfigControl.GetConnectionString();
|
connectionString = databaseConfigControl.GetConnectionString();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (connectionString != "" && _hostUsername != "" && _hostPassword.Length >= 6 && _hostPassword == _confirmPassword && _hostEmail != "" && _hostEmail.Contains("@"))
|
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 = "";
|
_loadingDisplay = "";
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
@@ -190,13 +260,17 @@
|
|||||||
DatabaseType = database.DBType,
|
DatabaseType = database.DBType,
|
||||||
ConnectionString = connectionString,
|
ConnectionString = connectionString,
|
||||||
Aliases = uri.Authority,
|
Aliases = uri.Authority,
|
||||||
HostEmail = _hostEmail,
|
HostUsername = _hostUsername,
|
||||||
HostPassword = _hostPassword,
|
HostPassword = _hostPassword,
|
||||||
HostName = UserNames.Host,
|
HostEmail = _hostEmail,
|
||||||
|
HostName = _hostName,
|
||||||
TenantName = TenantNames.Master,
|
TenantName = TenantNames.Master,
|
||||||
IsNewTenant = true,
|
IsNewTenant = true,
|
||||||
SiteName = Constants.DefaultSite,
|
SiteName = Constants.DefaultSite,
|
||||||
Register = _register
|
Register = _register,
|
||||||
|
SiteTemplate = _template,
|
||||||
|
RenderMode = RenderModes.Static,
|
||||||
|
Runtime = Runtimes.Server
|
||||||
};
|
};
|
||||||
|
|
||||||
var installation = await InstallationService.Install(config);
|
var installation = await InstallationService.Install(config);
|
||||||
@@ -211,9 +285,56 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
_message = string.Join("<br />", result.Errors.Select(i => !string.IsNullOrEmpty(i.Value) ? i.Value : Localizer[i.Key]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
_message = Localizer["Message.Require.DbInfo"];
|
_message = Localizer["Message.Require.DbInfo"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
@foreach (var p in _pages)
|
@foreach (var p in _pages)
|
||||||
{
|
{
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||||
{
|
{
|
||||||
string url = NavigateUrl(p.Path);
|
string url = NavigateUrl(p.Path);
|
||||||
<div class="col-md-2 mx-auto text-center">
|
<div class="col-md-2 mx-auto text-center my-3">
|
||||||
<NavLink class="nav-link" href="@url" Match="NavLinkMatch.All">
|
<NavLink class="nav-link text-body" href="@url" Match="NavLinkMatch.All">
|
||||||
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@SharedLocalizer[p.Name]
|
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>
|
||||||
|
<div class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</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,60 +4,66 @@
|
|||||||
@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
|
||||||
|
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Upload" Heading="Upload Files" ResourceKey="UploadFiles">
|
<TabPanel Name="Upload" Heading="Upload Files" ResourceKey="UploadFiles">
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="upload" HelpText="Upload the file you want" ResourceKey="Upload">Upload: </Label>
|
||||||
<Label For="upload" HelpText="Upload the file you want" ResourceKey="Upload">Upload: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<FileManager UploadMultiple="true" ShowFiles="false" FolderId="@_folderId" ShowSuccess="true" />
|
||||||
<td>
|
</div>
|
||||||
<FileManager UploadMultiple="true" ShowFiles="false" FolderId="@_folderId" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Download" Heading="Download Files" ResourceKey="DownloadFiles">
|
<TabPanel Name="Download" Heading="Download Files" ResourceKey="DownloadFiles">
|
||||||
@if (_folders != null)
|
@if (_folders != null)
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<tr>
|
<div class="container">
|
||||||
<td>
|
<div class="row mb-1 align-items-center">
|
||||||
<Label For="url" HelpText="Enter the url of the file you wish to download" ResourceKey="Url">Url: </Label>
|
<Label Class="col-sm-3" For="url" HelpText="Enter the url of the file you wish to download" ResourceKey="Url">Url: </Label>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="url" class="form-control" @bind="@_url" required />
|
||||||
<input id="url" class="form-control" @bind="@url" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="folder" HelpText="Select the folder to save the file in" ResourceKey="Folder">Folder: </Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="folder" HelpText="Select the folder to save the file in" ResourceKey="Folder">Folder: </Label>
|
<select id="folder" class="form-select" @bind="@_folderId" required>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="folder" class="form-select" @bind="@_folderId">
|
|
||||||
<option value="-1"><@Localizer["Folder.Select"]></option>
|
<option value="-1"><@Localizer["Folder.Select"]></option>
|
||||||
@foreach (Folder folder in _folders)
|
@foreach (Folder folder in _folders)
|
||||||
{
|
{
|
||||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="name" HelpText="Enter the name of the file being downloaded" ResourceKey="Name">Name: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="name" class="form-control" @bind="@_name" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button type="button" class="btn btn-success" @onclick="Download">@SharedLocalizer["Download"]</button>
|
<button type="button" class="btn btn-success" @onclick="Download">@SharedLocalizer["Download"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</form>
|
||||||
}
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string url = string.Empty;
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
private string _url = string.Empty;
|
||||||
private List<Folder> _folders;
|
private List<Folder> _folders;
|
||||||
private int _folderId = -1;
|
private int _folderId = -1;
|
||||||
|
private string _name = "";
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
@@ -73,22 +79,29 @@
|
|||||||
|
|
||||||
private async Task Download()
|
private async Task Download()
|
||||||
{
|
{
|
||||||
if (url == string.Empty || _folderId == -1)
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
|
||||||
|
if (await interop.FormValid(form))
|
||||||
|
{
|
||||||
|
if (_url == string.Empty || _folderId == -1)
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Required.UrlFolder"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Required.UrlFolder"], MessageType.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var filename = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1);
|
if (string.IsNullOrEmpty(_name))
|
||||||
|
{
|
||||||
|
_name = _url.Substring(_url.LastIndexOf("/", StringComparison.Ordinal) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
if (!Constants.UploadableFiles.Split(',')
|
if (!PageState.Site.UploadableFiles.Split(',').Contains(Path.GetExtension(_name).ToLower().Replace(".", "")))
|
||||||
.Contains(Path.GetExtension(filename).ToLower().Replace(".", "")))
|
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Download.InvalidExtension"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Download.InvalidExtension"], MessageType.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!filename.IsPathOrFileValid())
|
if (!_name.IsPathOrFileValid())
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Required.UrlName"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Required.UrlName"], MessageType.Warning);
|
||||||
return;
|
return;
|
||||||
@@ -96,14 +109,19 @@
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await FileService.UploadFileAsync(url, _folderId);
|
await FileService.UploadFileAsync(_url, _folderId, _name);
|
||||||
await logger.LogInformation("File Downloaded Successfully From Url {Url}", url);
|
await logger.LogInformation("File Downloaded Successfully From Url {Url}", _url);
|
||||||
AddModuleMessage(Localizer["Success.Download.File"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.Download.File"], MessageType.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Downloading File From Url {Url} {Error}", url, ex.Message);
|
await logger.LogError(ex, "Error Downloading File From Url {Url} {Error}", _url, ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.Download.InvalidUrl"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Download.InvalidUrl"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,49 +8,58 @@
|
|||||||
|
|
||||||
@if (_folders != null)
|
@if (_folders != null)
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<tr>
|
<div class="container">
|
||||||
<td width="30%">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label for="name" HelpText="The name of the file" ResourceKey="Name">Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="The name of the file" ResourceKey="Name">Name: </Label>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
||||||
<input id="name" class="form-control" @bind="@_name" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="parent" HelpText="The folder where the file is located" ResourceKey="Folder">Folder: </Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="parent" HelpText="The folder where the file is located" ResourceKey="Folder">Folder: </Label>
|
<select id="parent" class="form-select" @bind="@_folderId" required>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="parent" class="form-select" @bind="@_folderId">
|
|
||||||
@foreach (Folder folder in _folders)
|
@foreach (Folder folder in _folders)
|
||||||
{
|
{
|
||||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="description" HelpText="A description of the file. This can be used as a caption for image files." ResourceKey="Description">Description: </Label>
|
||||||
<Label for="size" HelpText="The size of the file (in bytes)" ResourceKey="Size">Size: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="description" class="form-control" @bind="@_description" />
|
||||||
<td>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="size" HelpText="The size of the file (in bytes)" ResourceKey="Size">Size: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input id="size" class="form-control" @bind="@_size" readonly />
|
<input id="size" class="form-control" @bind="@_size" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
</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>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
private int _fileId = -1;
|
private int _fileId = -1;
|
||||||
private string _name;
|
private string _name;
|
||||||
private List<Folder> _folders;
|
private List<Folder> _folders;
|
||||||
private int _folderId = -1;
|
private int _folderId = -1;
|
||||||
|
private string _description = string.Empty;
|
||||||
private int _size;
|
private int _size;
|
||||||
private string _createdBy;
|
private string _createdBy;
|
||||||
private DateTime _createdOn;
|
private DateTime _createdOn;
|
||||||
@@ -72,6 +81,7 @@
|
|||||||
{
|
{
|
||||||
_name = file.Name;
|
_name = file.Name;
|
||||||
_folderId = file.FolderId;
|
_folderId = file.FolderId;
|
||||||
|
_description = file.Description;
|
||||||
_size = file.Size;
|
_size = file.Size;
|
||||||
_createdBy = file.CreatedBy;
|
_createdBy = file.CreatedBy;
|
||||||
_createdOn = file.CreatedOn;
|
_createdOn = file.CreatedOn;
|
||||||
@@ -87,6 +97,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveFile()
|
private async Task SaveFile()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -95,6 +109,7 @@
|
|||||||
File file = await FileService.GetFileAsync(_fileId);
|
File file = await FileService.GetFileAsync(_fileId);
|
||||||
file.Name = _name;
|
file.Name = _name;
|
||||||
file.FolderId = _folderId;
|
file.FolderId = _folderId;
|
||||||
|
file.Description = _description;
|
||||||
file = await FileService.UpdateFileAsync(file);
|
file = await FileService.UpdateFileAsync(file);
|
||||||
await logger.LogInformation("File Saved {File}", file);
|
await logger.LogInformation("File Saved {File}", file);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
@@ -110,4 +125,23 @@
|
|||||||
AddModuleMessage(Localizer["Error.File.Save"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.File.Save"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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,57 +8,95 @@
|
|||||||
|
|
||||||
@if (_folders != null)
|
@if (_folders != null)
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<TabStrip>
|
||||||
<tr>
|
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
|
||||||
<td width="30%">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<Label For="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
|
<div class="container">
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
|
||||||
<select id="parent" class="form-select" @bind="@_parentId">
|
<div class="col-sm-9">
|
||||||
@if (PageState.QueryString.ContainsKey("id"))
|
@if (_parentId == -1)
|
||||||
{
|
{
|
||||||
|
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||||
<option value="-1"><@Localizer["NoParent"]></option>
|
<option value="-1"><@Localizer["NoParent"]></option>
|
||||||
|
</select>
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||||
@foreach (Folder folder in _folders)
|
@foreach (Folder folder in _folders)
|
||||||
{
|
{
|
||||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
}
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>
|
<div class="row mb-1 align-items-center">
|
||||||
<Label for="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
<td>
|
@if (_isSystem)
|
||||||
<input id="name" class="form-control" @bind="@_name" />
|
{
|
||||||
</td>
|
<input id="name" class="form-control" @bind="@_name" readonly />
|
||||||
</tr>
|
}
|
||||||
<tr>
|
else
|
||||||
<td>
|
{
|
||||||
<Label for="type" HelpText="Select the folder type. Private folders are only accessible by authorized users. Public folders can be accessed by all users" ResourceKey="Name">Type: </Label>
|
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
||||||
</td>
|
}
|
||||||
<td>
|
</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"))
|
@if (PageState.QueryString.ContainsKey("id"))
|
||||||
{
|
{
|
||||||
<input id="type" class="form-control" readonly @bind="@_type" />
|
<input id="type" class="form-control" readonly @bind="@_type" />
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<select id="type" class="form-select" @bind="@_type">
|
<select id="type" class="form-select" @bind="@_type" required>
|
||||||
<option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option>
|
<option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option>
|
||||||
<option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option>
|
<option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option>
|
||||||
</select>
|
</select>
|
||||||
}
|
}
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td colspan="2" align="center">
|
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
|
||||||
<Label For="permissions" HelpText="Select the permissions you want for the folder" ResourceKey="Permissions">Permissions: </Label>
|
<div class="col-sm-9">
|
||||||
<PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="@(PermissionNames.Browse + "," + PermissionNames.View + "," + PermissionNames.Edit)" Permissions="@_permissions" @ref="_permissionGrid" />
|
<input id="capacity" class="form-control" @bind="@_capacity" required />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
<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>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
|
|
||||||
|
<br />
|
||||||
@if (!_isSystem)
|
@if (!_isSystem)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveFolder">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveFolder">@SharedLocalizer["Save"]</button>
|
||||||
@@ -70,22 +108,21 @@
|
|||||||
@((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 {
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
private List<Folder> _folders;
|
private List<Folder> _folders;
|
||||||
private int _folderId = -1;
|
private int _folderId = -1;
|
||||||
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 _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;
|
||||||
@@ -114,8 +151,11 @@
|
|||||||
_parentId = folder.ParentId ?? -1;
|
_parentId = folder.ParentId ?? -1;
|
||||||
_name = folder.Name;
|
_name = folder.Name;
|
||||||
_type = folder.Type;
|
_type = folder.Type;
|
||||||
|
_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;
|
||||||
@@ -125,7 +165,6 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
_parentId = _folders[0].FolderId;
|
_parentId = _folders[0].FolderId;
|
||||||
_permissions = string.Empty;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -136,6 +175,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveFolder()
|
private async Task SaveFolder()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
if (_name == string.Empty || _parentId == -1)
|
if (_name == string.Empty || _parentId == -1)
|
||||||
{
|
{
|
||||||
@@ -152,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);
|
||||||
@@ -161,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;
|
||||||
@@ -172,10 +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.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)
|
||||||
{
|
{
|
||||||
@@ -188,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());
|
||||||
}
|
}
|
||||||
@@ -203,6 +255,11 @@
|
|||||||
AddModuleMessage(Localizer["Error.Folder.Save"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Folder.Save"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task DeleteFolder()
|
private async Task DeleteFolder()
|
||||||
{
|
{
|
||||||
@@ -218,20 +275,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isparent)
|
if (!isparent)
|
||||||
{
|
|
||||||
var files = await FileService.GetFilesAsync(_folderId);
|
|
||||||
if (files.Count == 0)
|
|
||||||
{
|
{
|
||||||
await FolderService.DeleteFolderAsync(_folderId);
|
await FolderService.DeleteFolderAsync(_folderId);
|
||||||
await logger.LogInformation("Folder Deleted {Folder}", _folderId);
|
await logger.LogInformation("Folder Deleted {Folder}", _folderId);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Folder.Files.InvalidDelete"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Folder.Subfolders.InvalidDelete"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Folder.Subfolders.InvalidDelete"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,32 +3,44 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (_files != null)
|
@if (_files == null)
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<p>
|
||||||
<tr>
|
<em>@SharedLocalizer["Loading"]</em>
|
||||||
<td>
|
</p>
|
||||||
<label class="control-label">@Localizer["Folder"] </label>
|
}
|
||||||
</td>
|
else
|
||||||
<td>
|
{
|
||||||
|
<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))">
|
<select class="form-select" @onchange="(e => FolderChanged(e))">
|
||||||
@foreach (Folder folder in _folders)
|
@foreach (Folder folder in _folders)
|
||||||
{
|
{
|
||||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />
|
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />
|
||||||
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md mb-1 text-end">
|
||||||
<ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
|
<ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
|
||||||
<Pager Items="@_files">
|
@if (_files.Count != 0)
|
||||||
|
{
|
||||||
|
<Pager Items="@_files" SearchProperties="Name">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -41,15 +53,42 @@
|
|||||||
<td><ActionLink Action="Details" Text="Edit" Parameters="@($"id=" + context.FileId.ToString())" ResourceKey="Details" /></td>
|
<td><ActionLink Action="Details" Text="Edit" Parameters="@($"id=" + context.FileId.ToString())" ResourceKey="Details" /></td>
|
||||||
<td><ActionDialog Header="Delete File" Message="@string.Format(Localizer["Confirm.File.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFile(context))" ResourceKey="DeleteFile" /></td>
|
<td><ActionDialog Header="Delete File" Message="@string.Format(Localizer["Confirm.File.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFile(context))" ResourceKey="DeleteFile" /></td>
|
||||||
<td><a href="@context.Url" target="_new">@context.Name</a></td>
|
<td><a href="@context.Url" target="_new">@context.Name</a></td>
|
||||||
<td>@context.ModifiedOn</td>
|
<td>@UtcToLocal(context.ModifiedOn)</td>
|
||||||
<td>@context.Extension.ToUpper() @SharedLocalizer["File"]</td>
|
<td>@context.Extension.ToUpper() @SharedLocalizer["File"]</td>
|
||||||
<td>@string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB</td>
|
<td>@string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@if (_files.Count == 0)
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
<div class="text-center">@Localizer["NoFiles"]</div>
|
<div class="text-center">@Localizer["NoFiles"]</div>
|
||||||
}
|
}
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="imageExt" HelpText="Enter a comma separated list of image file extensions" ResourceKey="ImageExtensions">Image Extensions: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="imageExt" spellcheck="false" class="form-control" @bind="@_imageFiles" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="uploadableFileExt" HelpText="Enter a comma separated list of uploadable file extensions" ResourceKey="UploadableFileExtensions">Uploadable File Extensions: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="uploadableFileExt" spellcheck="false" class="form-control" @bind="@_uploadableFiles" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="maxChunkSize" HelpText="Files are split into chunks to streamline the upload process. Specify the maximum chunk size in MB (note that higher chunk sizes should only be used on faster networks)." ResourceKey="MaxChunkSize">Max Upload Chunk Size (MB): </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="maxChunkSize" type="number" min="1" max="10" step="1" class="form-control" @bind="@_maxChunkSize" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -57,6 +96,10 @@
|
|||||||
private int _folderId = -1;
|
private int _folderId = -1;
|
||||||
private List<File> _files;
|
private List<File> _files;
|
||||||
|
|
||||||
|
private string _imageFiles = string.Empty;
|
||||||
|
private string _uploadableFiles = string.Empty;
|
||||||
|
private int _maxChunkSize = 1;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
@@ -70,6 +113,13 @@
|
|||||||
_folderId = _folders[0].FolderId;
|
_folderId = _folders[0].FolderId;
|
||||||
await GetFiles();
|
await GetFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
||||||
|
_imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles;
|
||||||
|
_uploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles);
|
||||||
|
_uploadableFiles = (string.IsNullOrEmpty(_uploadableFiles)) ? Constants.UploadableFiles : _uploadableFiles;
|
||||||
|
_maxChunkSize = int.Parse(SettingService.GetSetting(settings, "MaxChunkSize", "1"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -114,4 +164,23 @@
|
|||||||
AddModuleMessage(string.Format(Localizer["Error.File.Delete"], file.Name), MessageType.Error);
|
AddModuleMessage(string.Format(Localizer["Error.File.Delete"], file.Name), MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task SaveSiteSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "ImageFiles", (_imageFiles != Constants.ImageFiles) ? _imageFiles.Replace(" ", "") : "", false);
|
||||||
|
settings = SettingService.SetSetting(settings, "UploadableFiles", (_uploadableFiles != Constants.UploadableFiles) ? _uploadableFiles.Replace(" ", "") : "", false);
|
||||||
|
settings = SettingService.SetSetting(settings, "MaxChunkSize", _maxChunkSize.ToString(), false);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
19
Oqtane.Client/Modules/Admin/Files/ModuleInfo.cs
Normal file
19
Oqtane.Client/Modules/Admin/Files/ModuleInfo.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Oqtane.Documentation;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Modules.Admin.Files
|
||||||
|
{
|
||||||
|
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
|
||||||
|
public class ModuleInfo : IModule
|
||||||
|
{
|
||||||
|
public ModuleDefinition ModuleDefinition => new ModuleDefinition
|
||||||
|
{
|
||||||
|
Name = "File Management",
|
||||||
|
Description = "File Management",
|
||||||
|
Version = Constants.Version,
|
||||||
|
Categories = "Admin",
|
||||||
|
ServerManagerType = "Oqtane.Modules.Admin.Files.Manager.FileManager, Oqtane.Server"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,98 +5,113 @@
|
|||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<table class="table table-borderless">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<tr>
|
<div class="container">
|
||||||
<td width="30%">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label For="name" HelpText="Enter the job name" ResourceKey="Name">Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="Enter the job name" ResourceKey="Name">Name: </Label>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
|
||||||
<input id="name" class="form-control" @bind="@_name" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="type" HelpText="The fully qualified job type name" ResourceKey="Type">Type: </Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="type" HelpText="The fully qualified job type name" ResourceKey="Type">Type: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="type" class="form-control" @bind="@_jobType" readonly />
|
<input id="type" class="form-control" @bind="@_jobType" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label>
|
||||||
<Label For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="enabled" class="form-select" @bind="@_isEnabled" required>
|
||||||
<td>
|
|
||||||
<select id="enabled" class="form-select" @bind="@_isEnabled">
|
|
||||||
<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>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
|
||||||
<Label For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="runs-every" class="form-control mb-1" @bind="@_interval" maxlength="4" required />
|
||||||
<td>
|
<select id="runs-every" class="form-select" @bind="@_frequency" required>
|
||||||
<input id="runs-every" class="form-control" @bind="@_interval" />
|
|
||||||
<select id="runs-every" class="form-select" @bind="@_frequency">
|
|
||||||
<option value="m">@Localizer["Minute(s)"]</option>
|
<option value="m">@Localizer["Minute(s)"]</option>
|
||||||
<option value="H">@Localizer["Hour(s)"]</option>
|
<option value="H">@Localizer["Hour(s)"]</option>
|
||||||
<option value="d">@Localizer["Day(s)"]</option>
|
<option value="d">@Localizer["Day(s)"]</option>
|
||||||
|
<option value="w">@Localizer["Week(s)"]</option>
|
||||||
<option value="M">@Localizer["Month(s)"]</option>
|
<option value="M">@Localizer["Month(s)"]</option>
|
||||||
|
<option value="O">@Localizer["Once"]</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
|
||||||
<Label For="starting" HelpText="What time do you want the job to start" ResourceKey="Starting">Starting: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="retention" type="number" min="0" step ="1" class="form-control" @bind="@_retentionHistory" maxlength="3" required />
|
||||||
<td>
|
</div>
|
||||||
<input id="starting" class="form-control" @bind="@_startDate" />
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
</tr>
|
<Label Class="col-sm-3" For="starting" HelpText="Optionally enter the date and time when this job should start executing" ResourceKey="Starting">Starting: </Label>
|
||||||
<tr>
|
<div class="col-sm-9">
|
||||||
<td>
|
<div class="row">
|
||||||
<Label For="ending" HelpText="When do you want the job to end" ResourceKey="Ending">Ending: </Label>
|
<div class="col">
|
||||||
</td>
|
<input id="starting" type="date" class="form-control" @bind="@_startDate" />
|
||||||
<td>
|
</div>
|
||||||
<input id="ending" class="form-control" @bind="@_endDate" />
|
<div class="col">
|
||||||
</td>
|
<input id="starting" type="time" class="form-control" placeholder="hh:mm" @bind="@_startTime" />
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<Label For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="ending" HelpText="Optionally enter the date and time when this job should stop executing" ResourceKey="Ending">Ending: </Label>
|
||||||
<input id="retention" class="form-control" @bind="@_retentionHistory" />
|
<div class="col-sm-9">
|
||||||
</td>
|
<div class="row">
|
||||||
</tr>
|
<div class="col">
|
||||||
<tr>
|
<input id="ending" type="date" class="form-control" @bind="@_endDate" />
|
||||||
<td>
|
</div>
|
||||||
<Label For="next" HelpText="Next execution for this job." ResourceKey="NextExecution">Next Execution: </Label>
|
<div class="col">
|
||||||
</td>
|
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" />
|
||||||
<td>
|
</div>
|
||||||
<input id="next" class="form-control" @bind="@_nextExecution" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
<div class="row mb-1 align-items-center">
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveJob">@SharedLocalizer["Save"]</button>
|
<Label Class="col-sm-3" For="next" HelpText="Optionally modify the date and time when this job should execute next" ResourceKey="NextExecution">Next Execution: </Label>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<div class="col-sm-9">
|
||||||
<br />
|
<div class="row">
|
||||||
<br />
|
<div class="col">
|
||||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
<input id="next" type="date" class="form-control" @bind="@_nextDate" />
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<input id="next" type="time" class="form-control" placeholder="hh:mm" @bind="@_nextTime" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveJob">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
||||||
|
</form>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private ElementReference form;
|
||||||
|
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 string _startDate = string.Empty;
|
private DateTime? _startDate = null;
|
||||||
private string _endDate = string.Empty;
|
private DateTime? _startTime = null;
|
||||||
|
private DateTime? _endDate = null;
|
||||||
|
private DateTime? _endTime = null;
|
||||||
private string _retentionHistory = string.Empty;
|
private string _retentionHistory = string.Empty;
|
||||||
private string _nextExecution = string.Empty;
|
private DateTime? _nextDate = null;
|
||||||
|
private DateTime? _nextTime = null;
|
||||||
private string createdby;
|
private string createdby;
|
||||||
private DateTime createdon;
|
private DateTime createdon;
|
||||||
private string modifiedby;
|
private string modifiedby;
|
||||||
@@ -117,10 +132,13 @@
|
|||||||
_isEnabled = job.IsEnabled.ToString();
|
_isEnabled = job.IsEnabled.ToString();
|
||||||
_interval = job.Interval.ToString();
|
_interval = job.Interval.ToString();
|
||||||
_frequency = job.Frequency;
|
_frequency = job.Frequency;
|
||||||
_startDate = (job.StartDate != null) ? job.StartDate.ToString() : string.Empty;
|
_startDate = UtcToLocal(job.StartDate);
|
||||||
_endDate = (job.EndDate != null) ? job.EndDate.ToString() : string.Empty;
|
_startTime = UtcToLocal(job.StartDate);
|
||||||
|
_endDate = UtcToLocal(job.EndDate);
|
||||||
|
_endTime = UtcToLocal(job.EndDate);
|
||||||
_retentionHistory = job.RetentionHistory.ToString();
|
_retentionHistory = job.RetentionHistory.ToString();
|
||||||
_nextExecution = job.NextExecution.ToString();
|
_nextDate = UtcToLocal(job.NextExecution);
|
||||||
|
_nextTime = UtcToLocal(job.NextExecution);
|
||||||
createdby = job.CreatedBy;
|
createdby = job.CreatedBy;
|
||||||
createdon = job.CreatedOn;
|
createdon = job.CreatedOn;
|
||||||
modifiedby = job.ModifiedBy;
|
modifiedby = job.ModifiedBy;
|
||||||
@@ -136,43 +154,32 @@
|
|||||||
|
|
||||||
private async Task SaveJob()
|
private async Task SaveJob()
|
||||||
{
|
{
|
||||||
if (_name != string.Empty && !string.IsNullOrEmpty(_jobType) && _frequency != string.Empty && _interval != string.Empty && _retentionHistory != string.Empty)
|
if (!Utilities.ValidateEffectiveExpiryDates(_startDate, _endDate))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
var job = await JobService.GetJobAsync(_jobId);
|
var job = await JobService.GetJobAsync(_jobId);
|
||||||
job.Name = _name;
|
job.Name = _name;
|
||||||
job.JobType = _jobType;
|
job.JobType = _jobType;
|
||||||
job.IsEnabled = Boolean.Parse(_isEnabled);
|
job.IsEnabled = Boolean.Parse(_isEnabled);
|
||||||
job.Frequency = _frequency;
|
job.Frequency = _frequency;
|
||||||
|
if (job.Frequency == "O") // once
|
||||||
|
{
|
||||||
|
job.Interval = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
job.Interval = int.Parse(_interval);
|
job.Interval = int.Parse(_interval);
|
||||||
|
|
||||||
if (_startDate == string.Empty)
|
|
||||||
{
|
|
||||||
job.StartDate = null;
|
|
||||||
}
|
}
|
||||||
else
|
job.StartDate = LocalToUtc(_startDate.Value.Date.Add(_startTime.Value.TimeOfDay));
|
||||||
{
|
job.EndDate = LocalToUtc(_endDate.Value.Date.Add(_endTime.Value.TimeOfDay));
|
||||||
job.StartDate = DateTime.Parse(_startDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_endDate == string.Empty)
|
|
||||||
{
|
|
||||||
job.EndDate = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
job.EndDate = DateTime.Parse(_endDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_nextExecution == string.Empty)
|
|
||||||
{
|
|
||||||
job.NextExecution = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
job.NextExecution = DateTime.Parse(_nextExecution);
|
|
||||||
}
|
|
||||||
|
|
||||||
job.RetentionHistory = int.Parse(_retentionHistory);
|
job.RetentionHistory = int.Parse(_retentionHistory);
|
||||||
|
job.NextExecution = LocalToUtc(_nextDate.Value.Date.Add(_nextTime.Value.TimeOfDay));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,14 +10,11 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ActionLink Action="Log" Class="btn btn-secondary" Text="View Logs" ResourceKey="ViewJobs" />
|
<button type="button" class="btn btn-secondary" @onclick="Refresh">@Localizer["Refresh.Text"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="(async () => await Refresh())">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,6 +42,9 @@ else
|
|||||||
</td>
|
</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<ActionLink Action="Log" Class="btn btn-secondary" Text="View All Logs" ResourceKey="ViewLogs" />
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -53,7 +52,12 @@ else
|
|||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
|
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Host; } }
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await GetJobs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetJobs()
|
||||||
{
|
{
|
||||||
_jobs = await JobService.GetJobsAsync();
|
_jobs = await JobService.GetJobsAsync();
|
||||||
}
|
}
|
||||||
@@ -83,59 +87,69 @@ else
|
|||||||
|
|
||||||
private string DisplayFrequency(int interval, string frequency)
|
private string DisplayFrequency(int interval, string frequency)
|
||||||
{
|
{
|
||||||
var result = $"{Localizer["Every"]} {interval.ToString()} ";
|
var result = "";
|
||||||
switch (frequency)
|
switch (frequency)
|
||||||
{
|
{
|
||||||
case "m":
|
case "m":
|
||||||
result += Localizer["Minute"];
|
result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Minute"];
|
||||||
break;
|
break;
|
||||||
case "H":
|
case "H":
|
||||||
result += Localizer["Hour"];
|
result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Hour"];
|
||||||
break;
|
break;
|
||||||
case "d":
|
case "d":
|
||||||
result += Localizer["Day"];
|
result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Day"];
|
||||||
|
break;
|
||||||
|
case "w":
|
||||||
|
result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Week"];
|
||||||
break;
|
break;
|
||||||
case "M":
|
case "M":
|
||||||
result += Localizer["Month"];
|
result = $"{Localizer["Every"]} {interval.ToString()} " + Localizer["Month"];
|
||||||
|
break;
|
||||||
|
case "O":
|
||||||
|
result = Localizer["Once"];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interval > 1)
|
|
||||||
{
|
|
||||||
result += Localizer["s"];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DeleteJob(Job job)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await JobService.DeleteJobAsync(job.JobId);
|
|
||||||
await logger.LogInformation("Job Deleted {Job}", job);
|
|
||||||
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
|
||||||
{
|
{
|
||||||
await JobService.StartJobAsync(jobId);
|
await JobService.StartJobAsync(jobId);
|
||||||
|
await logger.LogInformation("Job Started {JobId}", jobId);
|
||||||
|
AddModuleMessage(Localizer["Message.Job.Start"], MessageType.Success);
|
||||||
|
_jobs = await JobService.GetJobsAsync();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Starting Job {JobId} {Error}", jobId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Job.Start"], MessageType.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StopJob(int jobId)
|
private async Task StopJob(int jobId)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
await JobService.StopJobAsync(jobId);
|
await JobService.StopJobAsync(jobId);
|
||||||
|
await logger.LogInformation("Job Stopped {JobId}", jobId);
|
||||||
|
AddModuleMessage(Localizer["Message.Job.Stop"], MessageType.Success);
|
||||||
|
_jobs = await JobService.GetJobsAsync();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Stopping Job {JobId} {Error}", jobId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.Job.Stop"], MessageType.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Refresh()
|
private async Task Refresh()
|
||||||
{
|
{
|
||||||
_jobs = await JobService.GetJobsAsync();
|
ShowProgressIndicator();
|
||||||
StateHasChanged();
|
await GetJobs();
|
||||||
|
HideProgressIndicator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Refresh">@Localizer["Refresh"]</button>
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
<Pager Items="@_jobLogs">
|
<Pager Items="@_jobLogs">
|
||||||
<Header>
|
<Header>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
@@ -20,8 +23,8 @@ else
|
|||||||
<Row>
|
<Row>
|
||||||
<td>@context.Job.Name</td>
|
<td>@context.Job.Name</td>
|
||||||
<td>@DisplayStatus(context.Job.IsExecuting, context.Succeeded)</td>
|
<td>@DisplayStatus(context.Job.IsExecuting, context.Succeeded)</td>
|
||||||
<td>@context.StartDate</td>
|
<td>@UtcToLocal(context.StartDate)</td>
|
||||||
<td>@context.FinishDate</td>
|
<td>@UtcToLocal(context.FinishDate)</td>
|
||||||
</Row>
|
</Row>
|
||||||
<Detail>
|
<Detail>
|
||||||
<td colspan="4">@((MarkupString)context.Notes)</td>
|
<td colspan="4">@((MarkupString)context.Notes)</td>
|
||||||
@@ -36,14 +39,17 @@ else
|
|||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
_jobLogs = await JobLogService.GetJobLogsAsync();
|
await GetJobLogs();
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("id"))
|
|
||||||
{
|
|
||||||
_jobLogs = _jobLogs.Where(item => item.JobId == Int32.Parse(PageState.QueryString["id"])).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_jobLogs = _jobLogs.OrderByDescending(item => item.JobLogId).ToList();
|
private async Task GetJobLogs()
|
||||||
|
{
|
||||||
|
var jobId = -1;
|
||||||
|
if (PageState.QueryString.ContainsKey("id"))
|
||||||
|
{
|
||||||
|
jobId = int.Parse(PageState.QueryString["id"]);
|
||||||
|
}
|
||||||
|
_jobLogs = await JobLogService.GetJobLogsAsync(jobId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string DisplayStatus(bool isExecuting, bool? succeeded)
|
private string DisplayStatus(bool isExecuting, bool? succeeded)
|
||||||
@@ -67,4 +73,11 @@ else
|
|||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task Refresh()
|
||||||
|
{
|
||||||
|
ShowProgressIndicator();
|
||||||
|
await GetJobLogs();
|
||||||
|
HideProgressIndicator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Languages
|
@namespace Oqtane.Modules.Admin.Languages
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@using System.Globalization
|
@using System.Globalization
|
||||||
@using Microsoft.AspNetCore.Localization
|
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject ILocalizationService LocalizationService
|
@inject ILocalizationService LocalizationService
|
||||||
@inject ILanguageService LanguageService
|
@inject ILanguageService LanguageService
|
||||||
@@ -9,179 +8,93 @@
|
|||||||
@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">
|
||||||
|
<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">
|
||||||
|
<select id="_code" class="form-select" @bind="@_code" required>
|
||||||
|
<option value="-"><@SharedLocalizer["Not Specified"]></option>
|
||||||
|
@foreach (var culture in _cultures)
|
||||||
{
|
{
|
||||||
<ModuleMessage Type="MessageType.Info" Message="@_message"></ModuleMessage>
|
<option value="@culture.Name">@(culture.DisplayName + " (" + culture.Name + ")")</option>
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<table class="table table-borderless">
|
|
||||||
<tr>
|
|
||||||
<td width="30%">
|
|
||||||
<Label For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="_code" class="form-select" @bind="@_code">
|
|
||||||
@foreach (var culture in _availableCultures)
|
|
||||||
{
|
|
||||||
<option value="@culture.Name">@culture.DisplayName</option>
|
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
|
||||||
<Label For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="default" class="form-select" @bind="@_default" required>
|
||||||
<td>
|
|
||||||
<select id="default" class="form-select" @bind="@_isDefault">
|
|
||||||
<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>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
|
|
||||||
}
|
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel Name="Download" ResourceKey="Download" Security="SecurityAccessLevel.Host">
|
|
||||||
<ModuleMessage Type="MessageType.Info" Message="Download one or more translations from the list below. Once you are ready click Install to complete the installation."></ModuleMessage>
|
|
||||||
|
|
||||||
<table class="table table-borderless" style=" margin: auto; width: 50% !important;">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
@if (_packages != null)
|
|
||||||
{
|
|
||||||
@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> | @SharedLocalizer["Search.Source"]: <strong>@context.PackageUrl</strong>
|
|
||||||
</td>
|
|
||||||
<td style="vertical-align: middle;">
|
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadLanguage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
|
||||||
</td>
|
|
||||||
</Row>
|
|
||||||
</Pager>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<br />
|
|
||||||
<div class="mx-auto text-center">
|
|
||||||
@Localizer["Search.NoResults"]
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
<button type="button" class="btn btn-success" @onclick="InstallLanguages">@SharedLocalizer["Install"]</button>
|
</div>
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
}
|
</form>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
|
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host" Heading="Upload">
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" HelpText="Upload one or more translations. Once they are uploaded click Install." ResourceKey="LanguageUpload">Translation: </Label>
|
||||||
<Label HelpText="Upload one or more translations. Once they are uploaded click Install to complete the installation." ResourceKey="Module">Language: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
|
||||||
<td>
|
</div>
|
||||||
<FileManager Filter="nupkg" ShowFiles="false" Folder="Packages" UploadMultiple="true" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<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>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _code = string.Empty;
|
private ElementReference form;
|
||||||
private string _isDefault = "False";
|
private bool validated = false;
|
||||||
private string _message;
|
|
||||||
private IEnumerable<Culture> _supportedCultures;
|
private string _code = "-";
|
||||||
private IEnumerable<Culture> _availableCultures;
|
private string _default = "False";
|
||||||
private List<Package> _packages;
|
private List<string> _languages;
|
||||||
private string _search = "";
|
private IEnumerable<Culture> _cultures;
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
_message = Localizer["OnlyEnglish"];
|
|
||||||
}
|
|
||||||
else if (_availableCultures.Count() == 0)
|
|
||||||
{
|
|
||||||
_message = Localizer["AllLanguages"];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadTranslations()
|
private async Task LoadCultures()
|
||||||
{
|
{
|
||||||
_packages = await PackageService.GetPackagesAsync("translation", _search);
|
_cultures = await LocalizationService.GetCulturesAsync(false);
|
||||||
}
|
_cultures = _cultures.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !_languages.Contains(c.Name));
|
||||||
|
_code = "-";
|
||||||
private async Task Search()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await LoadTranslations();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error On Search");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Reset()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_search = "";
|
|
||||||
await LoadTranslations();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error On Reset");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveLanguage()
|
private async Task SaveLanguage()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form) && _code != "-")
|
||||||
{
|
{
|
||||||
var language = new Language
|
var language = new Language
|
||||||
{
|
{
|
||||||
SiteId = PageState.Page.SiteId,
|
SiteId = PageState.Page.SiteId,
|
||||||
Name = CultureInfo.GetCultureInfo(_code).DisplayName,
|
|
||||||
Code = _code,
|
Code = _code,
|
||||||
IsDefault = (_isDefault == null ? false : Boolean.Parse(_isDefault))
|
IsDefault = (_default == null ? false : Boolean.Parse(_default))
|
||||||
};
|
};
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -203,33 +116,9 @@ else
|
|||||||
AddModuleMessage(Localizer["Error.Language.Add"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Language.Add"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private async Task InstallLanguages()
|
|
||||||
{
|
{
|
||||||
try
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
{
|
|
||||||
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 DownloadLanguage(string packageid, string version)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await PackageService.DownloadPackageAsync(packageid, version, "Packages");
|
|
||||||
await logger.LogInformation("Language Paclage {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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,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, forceLoad: 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>
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
<th style="width: 1px;">@Localizer["Translation"]</th>
|
||||||
<th style="width: 1px;"> </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))
|
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@SharedLocalizer["Upgrade"]</button>
|
<td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</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>
|
</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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
private async Task GetPackage(string code, string version)
|
||||||
return upgradeavailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DownloadLanguage(string code)
|
|
||||||
{
|
{
|
||||||
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, "Packages");
|
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.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);
|
AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HideModal()
|
||||||
|
{
|
||||||
|
_package = null;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,154 +1,254 @@
|
|||||||
|
@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
|
||||||
|
|
||||||
@if (_message != string.Empty)
|
@if (PageState.User != null)
|
||||||
{
|
{
|
||||||
<ModuleMessage Message="@_message" Type="@_type" />
|
|
||||||
}
|
|
||||||
<AuthorizeView Roles="@RoleNames.Registered">
|
|
||||||
<Authorizing>
|
|
||||||
<text>...</text>
|
|
||||||
</Authorizing>
|
|
||||||
<Authorized>
|
|
||||||
<ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
|
<ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
|
||||||
</Authorized>
|
}
|
||||||
<NotAuthorized>
|
else
|
||||||
|
{
|
||||||
|
@if (!twofactor)
|
||||||
|
{
|
||||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="container Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
||||||
|
@if (_allowexternallogin)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
@if (_allowsitelogin)
|
||||||
|
{
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="Username" class="control-label">@SharedLocalizer["Username"] </label>
|
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
||||||
<input type="text" @ref="username" name="Username" class="form-control username" placeholder="Username" @bind="@_username" id="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">
|
<div class="form-group mt-2">
|
||||||
<label for="Password" class="control-label">@SharedLocalizer["Password"] </label>
|
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
||||||
<input type="password" name="Password" class="form-control password" placeholder="Password" @bind="@_password" id="Password" required />
|
<div class="input-group">
|
||||||
|
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check form-check-inline">
|
|
||||||
<label class="form-check-label" for="Remember">@Localizer["RememberMe"]</label>
|
|
||||||
<input type="checkbox" class="form-check-input" name="Remember" @bind="@_remember" id="Remember" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group mt-2">
|
||||||
|
@if (!_alwaysremember)
|
||||||
|
{
|
||||||
|
<div class="form-check">
|
||||||
|
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
|
||||||
|
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
<br /><br />
|
<br />
|
||||||
|
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||||
|
@if (PageState.Site.AllowRegistration)
|
||||||
|
{
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
||||||
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</NotAuthorized>
|
}
|
||||||
</AuthorizeView>
|
else
|
||||||
|
{
|
||||||
|
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<div class="container Oqtane-Modules-Admin-Login">
|
||||||
|
<div class="form-group">
|
||||||
|
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
|
||||||
|
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _returnUrl = string.Empty;
|
private bool _allowsitelogin = true;
|
||||||
private string _message = string.Empty;
|
private bool _allowexternallogin = false;
|
||||||
private MessageType _type = MessageType.Info;
|
|
||||||
private string _username = string.Empty;
|
|
||||||
private string _password = string.Empty;
|
|
||||||
private bool _remember = false;
|
|
||||||
private bool validated = false;
|
|
||||||
|
|
||||||
private ElementReference login;
|
private ElementReference login;
|
||||||
|
private bool validated = false;
|
||||||
|
private bool twofactor = false;
|
||||||
|
private string _username = string.Empty;
|
||||||
private ElementReference username;
|
private ElementReference username;
|
||||||
|
private string _password = string.Empty;
|
||||||
|
private string _passwordtype = "password";
|
||||||
|
private string _togglepassword = string.Empty;
|
||||||
|
private bool _remember = false;
|
||||||
|
private bool _alwaysremember = false;
|
||||||
|
private string _code = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||||
|
public override bool? Prerender => true;
|
||||||
|
|
||||||
public override List<Resource> Resources => new List<Resource>()
|
public override List<Resource> Resources => new List<Resource>()
|
||||||
{
|
{
|
||||||
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()
|
||||||
{
|
{
|
||||||
if (PageState.QueryString.ContainsKey("returnurl"))
|
try
|
||||||
{
|
{
|
||||||
_returnUrl = PageState.QueryString["returnurl"];
|
_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"));
|
||||||
|
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("name"))
|
if (PageState.QueryString.ContainsKey("name"))
|
||||||
{
|
{
|
||||||
_username = PageState.QueryString["name"];
|
_username = PageState.QueryString["name"];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PageState.QueryString.ContainsKey("token"))
|
if (PageState.QueryString.ContainsKey("token") && !string.IsNullOrEmpty(_username))
|
||||||
{
|
{
|
||||||
var user = new User();
|
var user = new User();
|
||||||
user.SiteId = PageState.Site.SiteId;
|
user.SiteId = PageState.Site.SiteId;
|
||||||
user.Username = _username;
|
user.Username = _username;
|
||||||
user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]);
|
|
||||||
|
|
||||||
|
if (PageState.QueryString.ContainsKey("key"))
|
||||||
|
{
|
||||||
|
user = await UserService.LinkUserAsync(user, PageState.QueryString["token"], PageState.Site.Settings["ExternalLogin:ProviderType"], PageState.QueryString["key"], PageState.Site.Settings["ExternalLogin:ProviderName"]);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
_message = Localizer["Success.Account.Verified"];
|
await logger.LogInformation(LogFunction.Security, "External Login Linkage Successful For Username {Username}", _username);
|
||||||
|
AddModuleMessage(Localizer["Success.Account.Linked"], MessageType.Info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_message = Localizer["Message.Account.NotVerfied"];
|
await logger.LogError(LogFunction.Security, "External Login Linkage Failed For Username {Username}", _username);
|
||||||
_type = MessageType.Warning;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender && PageState.User == null && _allowsitelogin)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
|
||||||
{
|
{
|
||||||
await username.FocusAsync();
|
await username.FocusAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// redirect logged in user to specified page
|
||||||
|
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task Login()
|
private async Task Login()
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(login))
|
if (await interop.FormValid(login))
|
||||||
{
|
{
|
||||||
if (PageState.Runtime == Oqtane.Shared.Runtime.Server)
|
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
|
||||||
{
|
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
|
||||||
var user = new User();
|
|
||||||
user.SiteId = PageState.Site.SiteId;
|
|
||||||
user.Username = _username;
|
|
||||||
user.Password = _password;
|
|
||||||
user = await UserService.LoginUserAsync(user, false, false);
|
|
||||||
|
|
||||||
if (user.IsAuthenticated)
|
if (!twofactor)
|
||||||
{
|
{
|
||||||
await logger.LogInformation("Login Successful For Username {Username}", _username);
|
_remember = _alwaysremember || _remember;
|
||||||
// server-side Blazor needs to post to the Login page so that the cookies are set correctly
|
user = await UserService.LoginUserAsync(user, hybrid, _remember);
|
||||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
user = await UserService.VerifyTwoFactorAsync(user, _code);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user != null && user.IsAuthenticated)
|
||||||
|
{
|
||||||
|
await logger.LogInformation(LogFunction.Security, "Login Successful For {Username} From IP Address {IPAddress}", _username, SiteState.RemoteIPAddress);
|
||||||
|
|
||||||
|
// return url is not specified if user navigated directly to login page
|
||||||
|
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
|
||||||
|
|
||||||
|
if (hybrid)
|
||||||
|
{
|
||||||
|
// hybrid apps utilize an interactive login
|
||||||
|
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||||
|
authstateprovider.NotifyAuthenticationChanged();
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl(returnurl, true));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// post back to the Login page so that the cookies are set correctly
|
||||||
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(returnurl) };
|
||||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
||||||
await interop.SubmitForm(url, fields);
|
await interop.SubmitForm(url, fields);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await logger.LogInformation("Login Failed For Username {Username}", _username);
|
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
|
||||||
|
{
|
||||||
|
twofactor = true;
|
||||||
|
validated = false;
|
||||||
|
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!twofactor)
|
||||||
|
{
|
||||||
|
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
|
||||||
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// client-side Blazor
|
await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username);
|
||||||
var user = new User();
|
AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
|
||||||
user.SiteId = PageState.Site.SiteId;
|
|
||||||
user.Username = _username;
|
|
||||||
user.Password = _password;
|
|
||||||
user = await UserService.LoginUserAsync(user, true, _remember);
|
|
||||||
if (user.IsAuthenticated)
|
|
||||||
{
|
|
||||||
await logger.LogInformation("Login Successful For Username {Username}", _username);
|
|
||||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
|
||||||
authstateprovider.NotifyAuthenticationChanged();
|
|
||||||
NavigationManager.NavigateTo(NavigateUrl(_returnUrl, true));
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
await logger.LogInformation("Login Failed For Username {Username}", _username);
|
|
||||||
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,13 +257,21 @@
|
|||||||
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
|
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
|
||||||
{
|
{
|
||||||
if (_username != string.Empty)
|
if (_username != string.Empty)
|
||||||
{
|
{
|
||||||
@@ -171,21 +279,36 @@
|
|||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await UserService.ForgotPasswordAsync(user);
|
await UserService.ForgotPasswordAsync(user);
|
||||||
_message = "Please Check The Email Address Associated To Your User Account For A Password Reset Notification";
|
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
|
||||||
|
AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_message = "User Does Not Exist";
|
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
|
||||||
_type = MessageType.Warning;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_message = "Please Enter The Username Related To Your Account And Then Select The Forgot Password Option Again";
|
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Resetting Password {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.ResetPassword"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Reset()
|
||||||
|
{
|
||||||
|
twofactor = false;
|
||||||
|
_username = "";
|
||||||
|
_password = "";
|
||||||
|
ClearModuleMessage();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task KeyPressed(KeyboardEventArgs e)
|
private async Task KeyPressed(KeyboardEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -194,4 +317,24 @@
|
|||||||
await Login();
|
await Login();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordtype == "password")
|
||||||
|
{
|
||||||
|
_passwordtype = "text";
|
||||||
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordtype = "password";
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExternalLogin()
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,135 +9,111 @@
|
|||||||
@inject IStringLocalizer<Detail> Localizer
|
@inject IStringLocalizer<Detail> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<table class="table table-borderless">
|
@if (_initialized)
|
||||||
<tr>
|
{
|
||||||
<td width="30%">
|
<div class="container">
|
||||||
<Label For="dateTime" HelpText="The date and time of this log" ResourceKey="DateTime">Date/Time: </Label>
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="dateTime" HelpText="The date and time of this log" ResourceKey="DateTime">Date/Time: </Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<input id="dateTime" class="form-control" @bind="@_logDate" readonly />
|
<input id="dateTime" class="form-control" @bind="@_logDate" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="level" HelpText="The level of this log" ResourceKey="Level">Level: </Label>
|
||||||
<Label For="level" HelpText="The level of this log" ResourceKey="Level">Level: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="level" class="form-control" @bind="@_level" readonly />
|
<input id="level" class="form-control" @bind="@_level" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="feature" HelpText="The feature that was affected" ResourceKey="Feature">Feature: </Label>
|
||||||
<Label For="feature" HelpText="The feature that was affected" ResourceKey="Feature">Feature: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="feature" class="form-control" @bind="@_feature" readonly />
|
<input id="feature" class="form-control" @bind="@_feature" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="function" HelpText="The function that was performed" ResourceKey="Function">Function: </Label>
|
||||||
<Label For="function" HelpText="The function that was performed" ResourceKey="Function">Function: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="function" class="form-control" @bind="@_function" readonly />
|
<input id="function" class="form-control" @bind="@_function" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="category" HelpText="The fully qualified type type that was affected" ResourceKey="Category">Type Name: </Label>
|
||||||
<Label For="category" HelpText="The categories that were affected" ResourceKey="Category">Category: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="category" class="form-control" @bind="@_category" readonly />
|
<input id="category" class="form-control" @bind="@_category" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
@if (_pageName != string.Empty)
|
@if (_pageName != string.Empty)
|
||||||
{
|
{
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="page" HelpText="The page that was affected" ResourceKey="Page">Page: </Label>
|
||||||
<Label For="page" HelpText="The page that was affected" ResourceKey="Page">Page: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="page" class="form-control" @bind="@_pageName" readonly />
|
<input id="page" class="form-control" @bind="@_pageName" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
}
|
}
|
||||||
@if (_moduleTitle != string.Empty)
|
@if (_moduleTitle != string.Empty)
|
||||||
{
|
{
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="module" HelpText="The module that was affected" ResourceKey="Module">Module: </Label>
|
||||||
<Label For="module" HelpText="The module that was affected" ResourceKey="Module">Module: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="module" class="form-control" @bind="@_moduleTitle" readonly />
|
<input id="module" class="form-control" @bind="@_moduleTitle" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
}
|
}
|
||||||
@if (_username != string.Empty)
|
@if (_username != string.Empty)
|
||||||
{
|
{
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="user" HelpText="The user that caused this log" ResourceKey="User">User: </Label>
|
||||||
<Label For="user" HelpText="The user that caused this log" ResourceKey="User">User: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="user" class="form-control" @bind="@_username" readonly />
|
<input id="user" class="form-control" @bind="@_username" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
}
|
}
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="url" HelpText="The url the log comes from" ResourceKey="Url">Url: </Label>
|
||||||
<Label For="url" HelpText="The url the log comes from" ResourceKey="Url">Url: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="url" class="form-control" @bind="@_url" readonly />
|
<input id="url" class="form-control" @bind="@_url" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="template" HelpText="What the log is about" ResourceKey="Template">Template: </Label>
|
||||||
<Label For="template" HelpText="What the log is about" ResourceKey="Template">Template: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="template" class="form-control" @bind="@_template" readonly />
|
<input id="template" class="form-control" @bind="@_template" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="message" HelpText="The message that the system generated" ResourceKey="Message">Message: </Label>
|
||||||
<Label For="message" HelpText="The message that the system generated" class="control-label" ResourceKey="Message">Message: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<textarea id="message" class="form-control" @bind="@_message" rows="5" readonly></textarea>
|
<textarea id="message" class="form-control" @bind="@_message" rows="5" readonly></textarea>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
@if (!string.IsNullOrEmpty(_exception))
|
@if (!string.IsNullOrEmpty(_exception))
|
||||||
{
|
{
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="exception" HelpText="The exceptions generated by the system" ResourceKey="Exception">Exception: </Label>
|
||||||
<Label For="exception" HelpText="The exceptions generated by the system" ResourceKey="Exception">Exception: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<textarea id="exception" class="form-control" @bind="@_exception" rows="5" readonly></textarea>
|
<textarea id="exception" class="form-control" @bind="@_exception" rows="5" readonly></textarea>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
}
|
}
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="properties" HelpText="The properties that were affected" ResourceKey="Properties">Properties: </Label>
|
||||||
<Label For="properties" HelpText="The properties that were affected" ResourceKey="Properties">Properties: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<textarea id="properties" class="form-control" @bind="@_properties" rows="5" readonly></textarea>
|
<textarea id="properties" class="form-control" @bind="@_properties" rows="5" readonly></textarea>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="server" HelpText="The server that was affected" ResourceKey="Server">Server: </Label>
|
||||||
<Label For="server" HelpText="The server that was affected" ResourceKey="Server">Server: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="server" class="form-control" @bind="@_server" readonly />
|
<input id="server" class="form-control" @bind="@_server" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
}
|
||||||
|
<NavLink class="btn btn-secondary" href="@CloseUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private bool _initialized = false;
|
||||||
private int _logId;
|
private int _logId;
|
||||||
private string _logDate = string.Empty;
|
private string _logDate = string.Empty;
|
||||||
private string _level = string.Empty;
|
private string _level = string.Empty;
|
||||||
@@ -154,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 string UrlParametersTemplate => "/{id}";
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
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;
|
||||||
@@ -179,7 +156,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log.PageId != null && log.ModuleId != null)
|
if (log.PageId != null && log.ModuleId != null && log.ModuleId != -1)
|
||||||
{
|
{
|
||||||
var pagemodule = await PageModuleService.GetPageModuleAsync(log.PageId.Value, log.ModuleId.Value);
|
var pagemodule = await PageModuleService.GetPageModuleAsync(log.PageId.Value, log.ModuleId.Value);
|
||||||
if (pagemodule != null)
|
if (pagemodule != null)
|
||||||
@@ -203,6 +180,7 @@
|
|||||||
_exception = log.Exception;
|
_exception = log.Exception;
|
||||||
_properties = log.Properties;
|
_properties = log.Properties;
|
||||||
_server = log.Server;
|
_server = log.Server;
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -211,4 +189,9 @@
|
|||||||
AddModuleMessage(Localizer["Error.Log.Load"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Log.Load"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string CloseUrl()
|
||||||
|
{
|
||||||
|
return (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
@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 IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@@ -10,11 +12,13 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<TabStrip>
|
||||||
<tr>
|
<TabPanel Name="Events" Heading="Events" ResourceKey="Events">
|
||||||
<td>
|
<div class="container g-0">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<div class="col-sm-4">
|
||||||
<Label For="level" HelpText="Select the log level for event log items" ResourceKey="Level">Level: </Label><br /><br />
|
<Label For="level" HelpText="Select the log level for event log items" ResourceKey="Level">Level: </Label><br /><br />
|
||||||
<select id="level" class="form-select" @onchange="(e => LevelChanged(e))">
|
<select id="level" class="form-select" value="@_level" @onchange="(e => LevelChanged(e))">
|
||||||
<option value="-"><@Localizer["AllLevels"]></option>
|
<option value="-"><@Localizer["AllLevels"]></option>
|
||||||
<option value="Trace">@Localizer["Trace"]</option>
|
<option value="Trace">@Localizer["Trace"]</option>
|
||||||
<option value="Debug">@Localizer["Debug"]</option>
|
<option value="Debug">@Localizer["Debug"]</option>
|
||||||
@@ -23,10 +27,10 @@ else
|
|||||||
<option value="Error">@Localizer["Error"]</option>
|
<option value="Error">@Localizer["Error"]</option>
|
||||||
<option value="Critical">@Localizer["Critical"]</option>
|
<option value="Critical">@Localizer["Critical"]</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
<div class="col-sm-4">
|
||||||
<Label For="function" HelpText="Select the function for event log items" ResourceKey="Function">Function: </Label><br /><br />
|
<Label For="function" HelpText="Select the function for event log items" ResourceKey="Function">Function: </Label><br /><br />
|
||||||
<select id="function" class="form-select" @onchange="(e => FunctionChanged(e))">
|
<select id="function" class="form-select" value="@_function" @onchange="(e => FunctionChanged(e))">
|
||||||
<option value="-"><@Localizer["AllFunctions"]></option>
|
<option value="-"><@Localizer["AllFunctions"]></option>
|
||||||
<option value="Create">@Localizer["Create"]</option>
|
<option value="Create">@Localizer["Create"]</option>
|
||||||
<option value="Read">@Localizer["Read"]</option>
|
<option value="Read">@Localizer["Read"]</option>
|
||||||
@@ -35,21 +39,22 @@ else
|
|||||||
<option value="Security">@Localizer["Security"]</option>
|
<option value="Security">@Localizer["Security"]</option>
|
||||||
<option value="Other">@Localizer["Other"]</option>
|
<option value="Other">@Localizer["Other"]</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
<div class="col-sm-4">
|
||||||
<Label For="rows" HelpText="Select the maximum number of event log items to review. Please note that if you choose more than 10 items the information will be split into pages." ResourceKey="Rows">Maximum Items: </Label><br /><br />
|
<Label For="rows" HelpText="Select the maximum number of event log items to review. Please note that if you choose more than 10 items the information will be split into pages." ResourceKey="Rows">Maximum Items: </Label><br /><br />
|
||||||
<select id="rows" class="form-select" @onchange="(e => RowsChanged(e))">
|
<select id="rows" class="form-select" value="@_rows" @onchange="(e => RowsChanged(e))">
|
||||||
<option value="10">10</option>
|
<option value="10">10</option>
|
||||||
<option value="50">50</option>
|
<option value="50">50</option>
|
||||||
<option value="100">100</option>
|
<option value="100">100</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
|
<br />
|
||||||
|
|
||||||
@if (_logs.Any())
|
@if (_logs.Any())
|
||||||
{
|
{
|
||||||
<Pager Items="@_logs">
|
<Pager Items="@_logs" CurrentPage="@_page.ToString()" OnPageChange="OnPageChange">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@Localizer["Date"]</th>
|
<th>@Localizer["Date"]</th>
|
||||||
@@ -58,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())" 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>
|
||||||
@@ -70,21 +75,59 @@ else
|
|||||||
{
|
{
|
||||||
<p><em>@Localizer["NoLogs"]</em></p>
|
<p><em>@Localizer["NoLogs"]</em></p>
|
||||||
}
|
}
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||||
|
<div class="container">
|
||||||
|
<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>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="retention" type="number" min="0" step="1" class="form-control" @bind="@_retention" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
|
<ActionDialog Header="Clear Events" Message="Are You Sure You Wish To Remove All Log Events?" Action="DeleteLogs" Class="btn btn-danger" OnClick="@(async () => await DeleteLogs())" ResourceKey="DeleteLogs" />
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
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 List<Log> _logs;
|
private List<Log> _logs;
|
||||||
|
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 (UrlParameters.ContainsKey("level"))
|
||||||
|
{
|
||||||
|
_level = UrlParameters["level"];
|
||||||
|
}
|
||||||
|
if (UrlParameters.ContainsKey("function"))
|
||||||
|
{
|
||||||
|
_function = UrlParameters["function"];
|
||||||
|
}
|
||||||
|
if (UrlParameters.ContainsKey("rows"))
|
||||||
|
{
|
||||||
|
_rows = UrlParameters["rows"];
|
||||||
|
}
|
||||||
|
if (UrlParameters.ContainsKey("page") && int.TryParse(UrlParameters["page"], out int page))
|
||||||
|
{
|
||||||
|
_page = page;
|
||||||
|
}
|
||||||
|
|
||||||
await GetLogs();
|
await GetLogs();
|
||||||
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_retention = int.Parse( SettingService.GetSetting(settings, "LogRetention", "30"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -170,4 +213,53 @@ else
|
|||||||
}
|
}
|
||||||
return classname;
|
return classname;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task SaveSiteSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "LogRetention", _retention.ToString(), true);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
|
||||||
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
_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,200 +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)
|
|
||||||
{
|
|
||||||
<table class="table table-borderless">
|
|
||||||
<tr>
|
|
||||||
<td width="30%">
|
|
||||||
<Label 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>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="owner" class="form-control" @bind="@_owner" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="module" HelpText="Enter a name for this module. It should not contain spaces or punctuation." ResourceKey="ModuleName">Module Name: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="module" class="form-control" @bind="@_module" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<textarea id="description" class="form-control" @bind="@_description" rows="3"></textarea>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="template" HelpText="Select a module template. Templates are located in the wwwroot/Modules/Templates folder on the server." ResourceKey="Template">Template: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="template" class="form-select" @onchange="(e => TemplateChanged(e))">
|
|
||||||
<option value="-"><@Localizer["Template.Select"]></option>
|
|
||||||
@foreach (Template template in _templates)
|
|
||||||
{
|
|
||||||
<option value="@template.Name">@template.Title</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="reference" class="form-select" @bind="@_reference">
|
|
||||||
@foreach (string version in _versions)
|
|
||||||
{
|
|
||||||
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
|
|
||||||
{
|
|
||||||
<option value="@(version)">@(version)</option>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
<option value="local">@SharedLocalizer["LocalVersion"]</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@if (!string.IsNullOrEmpty(_location))
|
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="module" class="form-control" @bind="@_location" readonly />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</table>
|
|
||||||
<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 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 async Task OnParametersSetAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_moduledefinitionname = SettingService.GetSetting(ModuleState.Settings, "ModuleDefinitionName", "");
|
|
||||||
_templates = await ModuleDefinitionService.GetModuleDefinitionTemplatesAsync();
|
|
||||||
_versions = Constants.ReleaseVersions.Split(',').Where(item => Version.Parse(item).CompareTo(Version.Parse("2.0.0")) >= 0).ToArray();
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_moduledefinitionname))
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Info.Module.Creator"], MessageType.Info);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Info.Module.Activate"], MessageType.Info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Loading Module Creator");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CreateModule()
|
|
||||||
{
|
|
||||||
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 = ModuleState.Settings;
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,15 +0,0 @@
|
|||||||
using Oqtane.Models;
|
|
||||||
|
|
||||||
namespace Oqtane.Modules.Admin.ModuleCreator
|
|
||||||
{
|
|
||||||
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,67 +8,178 @@
|
|||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Download" ResourceKey="Download">
|
<TabPanel Name="Download" ResourceKey="Download" Heading="Download">
|
||||||
<ModuleMessage Type="MessageType.Info" Message="Download one or more modules from the list below. Once you are ready click Install to complete the installation."></ModuleMessage>
|
<div class="row justify-content-center mb-3">
|
||||||
|
<div class="text-center">
|
||||||
<table class="table table-borderless" style="margin: auto; width: 50% !important;">
|
<div class="form-check form-check-inline">
|
||||||
<tr>
|
<input id="free" class="form-check-input" type="radio" checked="@(_price == "free")" name="Price" @onchange="@(() => PriceChanged("free"))" />
|
||||||
<td>
|
<label class="form-check-label" for="free">@SharedLocalizer["Free"]</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input id="paid" class="form-check-input" type="radio" checked="@(_price == "paid")" name="Price" @onchange="@(() => PriceChanged("paid"))" />
|
||||||
|
<label class="form-check-label" for="paid">@SharedLocalizer["Paid"]</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-center mb-3">
|
||||||
|
<div class="col">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">@Localizer["Product"]</span>
|
||||||
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
|
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
|
||||||
</td>
|
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
|
||||||
<td>
|
|
||||||
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
|
<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>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
|
</div>
|
||||||
@if (_packages != null)
|
<div class="row mb-3">
|
||||||
|
<div class="col">
|
||||||
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
if (_packages.Count > 0)
|
<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")
|
||||||
{
|
{
|
||||||
<Pager Items="@_packages">
|
<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>
|
<Row>
|
||||||
<td>
|
<div class="m-2 p-2 d-flex justify-content-center">
|
||||||
<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 />
|
<div class="container-fluid px-0">
|
||||||
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
<div class="row g-0 mb-2">
|
||||||
<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> | @SharedLocalizer["Search.Source"]: <strong>@context.PackageUrl</strong>
|
<div class="col-4">
|
||||||
</td>
|
<a href="@context.ProductUrl" target="_blank">
|
||||||
<td style="vertical-align: middle;">
|
@if (context.LogoUrl != null)
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadModule(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
{
|
||||||
</td>
|
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
|
||||||
</Row>
|
|
||||||
</Pager>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<br />
|
<img src="/package.png" class="img-fluid" alt="@context.Name" />
|
||||||
<div class="mx-auto text-center">
|
}
|
||||||
@Localizer["Search.NoResults"]
|
</a>
|
||||||
</div>
|
</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">
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" HelpText="Upload one or more module packages." ResourceKey="Module">Module: </Label>
|
||||||
<Label HelpText="Upload one or more module packages. Once they are uploaded click Install to complete the installation." ResourceKey="Module">Module: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
|
||||||
<td>
|
</div>
|
||||||
<FileManager Filter="nupkg" ShowFiles="false" Folder="Packages" UploadMultiple="true" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
|
|
||||||
<button type="button" class="btn btn-success" @onclick="InstallModules">@SharedLocalizer["Install"]</button>
|
@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(_packagelicense))
|
||||||
|
{
|
||||||
|
@((MarkupString)_packagelicense)
|
||||||
|
}
|
||||||
|
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>
|
||||||
|
}
|
||||||
|
|
||||||
<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 _sort = "popularity";
|
||||||
private string _search = "";
|
private string _search = "";
|
||||||
|
private string _productname = "";
|
||||||
|
private string _packageid = "";
|
||||||
|
private string _packagelicense = "";
|
||||||
|
private string _packageversion = "";
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
@@ -77,6 +188,7 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LoadModuleDefinitions();
|
await LoadModuleDefinitions();
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -87,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);
|
_packages = await PackageService.GetPackagesAsync("module", _search, _price, "", _sort);
|
||||||
|
|
||||||
if (_packages != null)
|
if (_packages != null)
|
||||||
{
|
{
|
||||||
@@ -100,59 +214,102 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HideProgressIndicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void PriceChanged(string price)
|
||||||
|
{
|
||||||
|
_price = price;
|
||||||
|
_sort = "popularity";
|
||||||
|
await LoadModuleDefinitions();
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
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 async Task InstallModules()
|
private void OnPageChange(int page)
|
||||||
|
{
|
||||||
|
_page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void SortChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_sort = (string)e.Value;
|
||||||
|
await LoadModuleDefinitions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HideModal()
|
||||||
|
{
|
||||||
|
_productname = "";
|
||||||
|
_packagelicense = "";
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetPackage(string packageid, string version)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ModuleDefinitionService.InstallModuleDefinitionsAsync();
|
var package = await PackageService.GetPackageAsync(packageid, version, false);
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Module.Install"], NavigateUrl("admin/system")), MessageType.Success);
|
if (package != null)
|
||||||
|
{
|
||||||
|
_productname = package.Name;
|
||||||
|
_packageid = package.PackageId;
|
||||||
|
if (!string.IsNullOrEmpty(package.License))
|
||||||
|
{
|
||||||
|
_packagelicense = package.License.Replace("\n", "<br />");
|
||||||
|
}
|
||||||
|
_packageversion = package.Version;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await logger.LogError("Error Getting Package {PackageId} {Version}", packageid, version);
|
||||||
|
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Installing Module");
|
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
|
||||||
|
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DownloadModule(string packageid, string version)
|
private async Task DownloadPackage()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PackageService.DownloadPackageAsync(packageid, version, "Packages");
|
await PackageService.DownloadPackageAsync(_packageid, _packageversion);
|
||||||
await logger.LogInformation("Module {ModuleDefinitionName} {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 = "";
|
||||||
|
_packagelicense = "";
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {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 void OnUpload()
|
||||||
|
{
|
||||||
|
AddModuleMessage(string.Format(Localizer["Success.Module.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,51 +10,42 @@
|
|||||||
|
|
||||||
@if (_templates != null)
|
@if (_templates != null)
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<tr>
|
<div class="container">
|
||||||
<td width="30%">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label 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>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="owner" class="form-control" @bind="@_owner" required />
|
||||||
<input id="owner" class="form-control" @bind="@_owner" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<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>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="module" HelpText="Enter a name for this module. It should not contain spaces or punctuation." ResourceKey="ModuleName">Module Name: </Label>
|
<input id="module" class="form-control" @bind="@_module" required />
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<input id="module" class="form-control" @bind="@_module" />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label>
|
||||||
</tr>
|
<div class="col-sm-9">
|
||||||
<tr>
|
<textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="2000"></textarea>
|
||||||
<td>
|
</div>
|
||||||
<Label For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label>
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<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>
|
||||||
<textarea id="description" class="form-control" @bind="@_description" rows="3"></textarea>
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="template" class="form-select" @onchange="(e => TemplateChanged(e))" required>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="template" HelpText="Select a module template. Templates are located in the wwwroot/Modules/Templates folder on the server." ResourceKey="Template">Template: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="template" class="form-select" @onchange="(e => TemplateChanged(e))">
|
|
||||||
<option value="-"><@Localizer["Template.Select"]></option>
|
<option value="-"><@Localizer["Template.Select"]></option>
|
||||||
@foreach (Template template in _templates)
|
@foreach (Template template in _templates)
|
||||||
{
|
{
|
||||||
<option value="@template.Name">@template.Title</option>
|
<option value="@template.Name">@template.Title</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
||||||
<Label For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="reference" class="form-select" @bind="@_reference" required>
|
||||||
<td>
|
|
||||||
<select id="reference" class="form-select" @bind="@_reference">
|
|
||||||
@foreach (string version in _versions)
|
@foreach (string version in _versions)
|
||||||
{
|
{
|
||||||
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
|
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
|
||||||
@@ -64,44 +55,52 @@
|
|||||||
}
|
}
|
||||||
<option value="local">@SharedLocalizer["LocalVersion"]</option>
|
<option value="local">@SharedLocalizer["LocalVersion"]</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
@if (!string.IsNullOrEmpty(_location))
|
@if (!string.IsNullOrEmpty(_location))
|
||||||
{
|
{
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
|
||||||
<Label For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="module" class="form-control" @bind="@_location" readonly />
|
<input id="module" class="form-control" @bind="@_location" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
}
|
}
|
||||||
</table>
|
</div>
|
||||||
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["CreateModule"]</button>
|
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["CreateModule"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</form>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
private string _owner = string.Empty;
|
private string _owner = string.Empty;
|
||||||
private string _module = string.Empty;
|
private string _module = string.Empty;
|
||||||
private string _description = string.Empty;
|
private string _description = string.Empty;
|
||||||
private List<Template> _templates;
|
private List<Template> _templates;
|
||||||
private string _template = "-";
|
private string _template = "-";
|
||||||
private string[] _versions;
|
private string[] _versions;
|
||||||
private string _reference = Constants.Version;
|
private string _reference = "local";
|
||||||
private string _minversion = "2.0.0";
|
private string _minversion = "2.0.0";
|
||||||
private string _location = string.Empty;
|
private string _location = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
if (!NavigationManager.BaseUri.Contains("localhost:"))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_templates = await ModuleDefinitionService.GetModuleDefinitionTemplatesAsync();
|
_templates = await ModuleDefinitionService.GetModuleDefinitionTemplatesAsync();
|
||||||
_versions = Constants.ReleaseVersions.Split(',').Where(item => Version.Parse(item).CompareTo(Version.Parse("2.0.0")) >= 0).ToArray();
|
_versions = Constants.ReleaseVersions.Split(',').Where(item => Version.Parse(item).CompareTo(Version.Parse("2.0.0")) >= 0).ToArray();
|
||||||
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -110,17 +109,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task CreateModule()
|
private async Task CreateModule()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
_owner = _owner.Trim();
|
||||||
|
_module = _module.Trim();
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
if (IsValidXML(_description))
|
||||||
|
{
|
||||||
|
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);
|
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
|
||||||
GetLocation();
|
GetLocation();
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Require.ValidDescription"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Require.ValidName"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Require.ValidName"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
@@ -130,11 +144,22 @@
|
|||||||
await logger.LogError(ex, "Error Creating Module");
|
await logger.LogError(ex, "Error Creating Module");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
@@ -155,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,137 +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">
|
{
|
||||||
<table class="table table-borderless">
|
<TabStrip>
|
||||||
<tr>
|
<TabPanel Name="Definition" ResourceKey="Definition" Heading="Definition">
|
||||||
<td width="30%">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<Label For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
|
<div class="container">
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="name" HelpText="The name of the module" ResourceKey="Name">Name: </Label>
|
||||||
<input id="name" class="form-control" @bind="@_name" />
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>
|
<div class="row mb-1 align-items-center">
|
||||||
<Label For="description" HelpText="The description of the module" ResourceKey="Description">Description: </Label>
|
<Label Class="col-sm-3" For="description" HelpText="The description of the module" ResourceKey="Description">Description: </Label>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
<td>
|
<textarea id="description" class="form-control" @bind="@_description" rows="2" maxlength="2000" required></textarea>
|
||||||
<textarea id="description" class="form-control" @bind="@_description" rows="2"></textarea>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="categories" HelpText="Comma delimited list of module categories" ResourceKey="Categories">Categories: </Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="categories" HelpText="Comma delimited list of module categories" ResourceKey="Categories">Categories: </Label>
|
<input id="categories" class="form-control" @bind="@_categories" maxlength="200" />
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<input id="categories" class="form-control" @bind="@_categories" />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="isenabled" HelpText="Is module enabled for this site?" ResourceKey="IsEnabled">Enabled? </Label>
|
||||||
</tr>
|
<div class="col-sm-9">
|
||||||
</table>
|
<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">
|
<Section Name="Information" ResourceKey="Information">
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="moduledefinitionname" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
||||||
<Label For="moduledefinitionname" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="moduledefinitionname" class="form-control" @bind="@_moduledefinitionname" disabled />
|
<input id="moduledefinitionname" class="form-control" @bind="@_moduledefinitionname" disabled />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="version" HelpText="The version of the module" ResourceKey="Version">Version: </Label>
|
||||||
<Label For="version" HelpText="The version of the module" ResourceKey="Version">Version: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="version" class="form-control" @bind="@_version" disabled />
|
<input id="version" class="form-control" @bind="@_version" disabled />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<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 For="owner" HelpText="The owner or creator of the module" ResourceKey="Owner">Owner: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||||
<td>
|
</div>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input id="owner" class="form-control" @bind="@_owner" disabled />
|
<input id="owner" class="form-control" @bind="@_owner" disabled />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="url" HelpText="The url of the module" ResourceKey="Url">Url: </Label>
|
||||||
<Label For="url" HelpText="The reference url of the module" ResourceKey="ReferenceUrl">Reference Url: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="url" class="form-control" @bind="@_url" disabled />
|
<input id="url" class="form-control" @bind="@_url" disabled />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="contact" HelpText="The contact for the module" ResourceKey="Contact">Contact: </Label>
|
||||||
<Label For="contact" HelpText="The contact for the module" ResourceKey="Contact">Contact: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="contact" class="form-control" @bind="@_contact" disabled />
|
<input id="contact" class="form-control" @bind="@_contact" disabled />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="license" HelpText="The module license terms" ResourceKey="License">License: </Label>
|
||||||
<Label For="license" HelpText="The module license terms" ResourceKey="License">License: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
@if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~"))
|
||||||
<td>
|
{
|
||||||
|
<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>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
|
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
|
||||||
</td>
|
}
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<Label For="runtimes" HelpText="The Blazor runtimes which this module supports" ResourceKey="Runtimes">Runtimes: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="runtimes" class="form-control" @bind="@_runtimes" disabled />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</Section>
|
</Section>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@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>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Permissions" ResourceKey="Permissions">
|
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
|
<PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
|
||||||
|
<Pager Items="@_pagesWithModules" RowClass="align-middle">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => NavigationManager.NavigateTo(Browse(context)))">@Localizer["Browse"]</button></td>
|
||||||
|
<td>@(string.IsNullOrEmpty(context.Title) ? @context.Name : @context.Title )</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Translations" ResourceKey="Translations" Heading="Translations">
|
||||||
|
@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>
|
<td>
|
||||||
<PermissionGrid EntityName="@EntityNames.ModuleDefinition" PermissionNames="@PermissionNames.Utilize" Permissions="@_permissions" @ref="_permissionGrid" />
|
@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>
|
</td>
|
||||||
</tr>
|
</Row>
|
||||||
</table>
|
</Pager>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<br />
|
||||||
|
<div class="mx-auto text-center">
|
||||||
|
@if (string.IsNullOrEmpty(_packagename))
|
||||||
|
{
|
||||||
|
@Localizer["Search.PackageNameMissing"]
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@Localizer["Search.NoResults"]
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
|
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
@if (_package != null)
|
||||||
<br />
|
{
|
||||||
<br />
|
<div class="app-actiondialog">
|
||||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
<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 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()
|
||||||
@@ -143,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)
|
||||||
@@ -167,8 +312,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveModuleDefinition()
|
private async Task SaveModuleDefinition()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
var moduleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
|
||||||
|
if (!moduleDefinitions.Any(item => item.Name.ToLower() == _name.ToLower() && item.ModuleDefinitionId != _moduleDefinitionId))
|
||||||
{
|
{
|
||||||
var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
|
var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
|
||||||
if (moduledefinition.Name != _name)
|
if (moduledefinition.Name != _name)
|
||||||
@@ -183,15 +335,97 @@
|
|||||||
{
|
{
|
||||||
moduledefinition.Categories = _categories;
|
moduledefinition.Categories = _categories;
|
||||||
}
|
}
|
||||||
moduledefinition.Permissions = _permissionGrid.GetPermissions();
|
moduledefinition.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled));
|
||||||
|
moduledefinition.PermissionList = _permissionGrid.GetPermissionList();
|
||||||
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
|
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
|
||||||
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);
|
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.DuplicateName"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Saving ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
|
await logger.LogError(ex, "Error Saving ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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
|
||||||
{
|
{
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-3 align-items-center">
|
||||||
|
<div class="col-sm-6">
|
||||||
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
|
||||||
@((MarkupString)" ")
|
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary ps-2" />
|
||||||
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary" />
|
<button type="button" class="btn btn-secondary pw-2" @onclick="@Synchronize">@Localizer["Synchronize"]</button>
|
||||||
|
</div>
|
||||||
|
<div 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,12 +44,16 @@ 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>@SharedLocalizer["Support"]</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" />
|
||||||
}
|
}
|
||||||
@@ -35,9 +61,38 @@ else
|
|||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
<td>@context.Version</td>
|
<td>@context.Version</td>
|
||||||
<td>
|
<td>
|
||||||
@if (UpgradeAvailable(context.PackageName, context.Version))
|
@if (context.IsEnabled)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadModule(context.PackageName, context.Version))>@SharedLocalizer["Upgrade"]</button>
|
<span>@SharedLocalizer["Yes"]</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>@SharedLocalizer["No"]</span>
|
||||||
|
}
|
||||||
|
</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>
|
||||||
|
@((MarkupString)PurchaseLink(context.PackageName))
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@{
|
||||||
|
var version = UpgradeAvailable(context.PackageName, context.Version);
|
||||||
|
}
|
||||||
|
@if (version != context.Version)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadModule(context.PackageName, version))>@SharedLocalizer["Upgrade"]</button>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -45,8 +100,12 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private List<Module> _modules;
|
||||||
|
private List<ModuleDefinition> _allModuleDefinitions;
|
||||||
private List<ModuleDefinition> _moduleDefinitions;
|
private List<ModuleDefinition> _moduleDefinitions;
|
||||||
private List<Package> _packages;
|
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;
|
||||||
|
|
||||||
@@ -54,8 +113,10 @@ else
|
|||||||
{
|
{
|
||||||
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();
|
||||||
|
await LoadModuleDefinitions();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -67,28 +128,69 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool UpgradeAvailable(string packagename, string version)
|
private async Task LoadModuleDefinitions()
|
||||||
{
|
{
|
||||||
var upgradeavailable = false;
|
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
|
||||||
if (_packages != null)
|
_packages = await PackageService.GetPackageUpdatesAsync("module");
|
||||||
|
}
|
||||||
|
|
||||||
|
private string PurchaseLink(string packagename)
|
||||||
|
{
|
||||||
|
string link = "";
|
||||||
|
if (!string.IsNullOrEmpty(packagename) && _packages != null)
|
||||||
{
|
{
|
||||||
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
|
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
|
||||||
if (package != null)
|
if (package != null)
|
||||||
{
|
{
|
||||||
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0);
|
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 upgradeavailable;
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DownloadModule(string packagename, string version)
|
private async Task DownloadModule(string packagename, string version)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PackageService.DownloadPackageAsync(packagename, version, "Packages");
|
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)
|
||||||
@@ -112,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,31 +5,97 @@
|
|||||||
@inject IStringLocalizer<Export> Localizer
|
@inject IStringLocalizer<Export> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<table class="table table-borderless">
|
<TabStrip>
|
||||||
<tbody>
|
<TabPanel Name="Content" Heading="Content" ResourceKey="Content">
|
||||||
<tr>
|
<div class="container">
|
||||||
<td width="30%">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label For="content" HelpText="Enter the module content" ResourceKey="Content">Content: </Label>
|
<Label Class="col-sm-3" For="content" HelpText="Select the Export option and you will be able to view the module content" ResourceKey="Content">Content: </Label>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
<td>
|
<textarea id="content" class="form-control" @bind="@_content" rows="5" readonly></textarea>
|
||||||
<textarea id="content" class="form-control" @bind="@_content" rows="5"></textarea>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</tbody>
|
<br />
|
||||||
</table>
|
<button type="button" class="btn btn-success" @onclick="ExportText">@Localizer["Export"]</button>
|
||||||
<button type="button" class="btn btn-success" @onclick="ExportModule">@Localizer["Export"]</button>
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@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>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _content = string.Empty;
|
private string _content = string.Empty;
|
||||||
|
private FileManager _filemanager;
|
||||||
|
private string _filename = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
public override string Title => "Export Content";
|
public override string Title => "Export Content";
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
private async Task ExportModule()
|
|
||||||
{
|
{
|
||||||
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId);
|
_filename = Utilities.GetFriendlyUrl(ModuleState.Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ExportText()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId, PageState.Page.PageId);
|
||||||
|
AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Exporting Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message);
|
||||||
|
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,38 +2,56 @@
|
|||||||
@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
|
||||||
|
|
||||||
<table class="table table-borderless">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<tbody>
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="file" HelpText="Optionally upload or select a file to import for this module" ResourceKey="File">File: </Label>
|
||||||
<Label For="content" HelpText="Enter the module content" ResourceKey="Content">Content: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<FileManager Filter="json" OnSelectFile="OnSelectFile" />
|
||||||
<td>
|
</div>
|
||||||
<textarea id="content" class="form-control" @bind="@_content" rows="5"></textarea>
|
</div>
|
||||||
</td>
|
<hr />
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
</tbody>
|
<Label Class="col-sm-3" For="content" HelpText="Provide the module content to import" ResourceKey="Content">Content: </Label>
|
||||||
</table>
|
<div class="col-sm-9">
|
||||||
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
|
<textarea id="content" class="form-control" @bind="@_content" rows="5" required></textarea>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</form>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _content = string.Empty;
|
private string _content = string.Empty;
|
||||||
|
private ElementReference form;
|
||||||
|
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;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
if (_content != string.Empty)
|
if (_content != string.Empty)
|
||||||
{
|
{
|
||||||
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);
|
||||||
@@ -54,4 +72,9 @@
|
|||||||
AddModuleMessage(Localizer["Message.Required.ImportContent"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Required.ImportContent"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,77 +3,128 @@
|
|||||||
@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
|
||||||
|
|
||||||
<TabStrip>
|
@if (_initialized)
|
||||||
|
{
|
||||||
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<TabStrip ActiveTab="@_activetab">
|
||||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||||
@if (_containers != null)
|
@if (_containers != null)
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="module" HelpText="The name of the module" ResourceKey="Module">Module: </Label>
|
||||||
<Label For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="module" type="text" class="form-control" @bind="@_module" disabled />
|
||||||
<td>
|
</div>
|
||||||
<input id="title" type="text" name="Title" class="form-control" @bind="@_title" />
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
</tr>
|
<Label Class="col-sm-3" For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label>
|
||||||
<tr>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="title" type="text" class="form-control" @bind="@_title" required />
|
||||||
<Label For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
<div class="row mb-1 align-items-center">
|
||||||
<select id="container" class="form-select" @bind="@_containerType">
|
<Label Class="col-sm-3" For="pane" HelpText="The pane where the module will be displayed" ResourceKey="Pane">Pane: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select class="form-select" @bind="@_pane">
|
||||||
|
@foreach (string pane in PageState.Page.Panes)
|
||||||
|
{
|
||||||
|
<option value="@pane">@pane Pane</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="container" class="form-select" @bind="@_containerType" required>
|
||||||
@foreach (var container in _containers)
|
@foreach (var container in _containers)
|
||||||
{
|
{
|
||||||
<option value="@container.TypeName">@container.Name</option>
|
<option value="@container.TypeName">@container.Name</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this module is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||||
<Label 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">
|
||||||
</td>
|
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||||
<td>
|
</div>
|
||||||
<select id="allpages" class="form-select" @bind="@_allPages">
|
</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="True">@SharedLocalizer["Yes"]</option>
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
|
||||||
<Label For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="page" class="form-select" @bind="@_pageId" required>
|
||||||
<td>
|
@if (PageState.Page.UserId != null)
|
||||||
<select id="page" class="form-select" @bind="@_pageId">
|
|
||||||
@foreach (Page p in PageState.Pages)
|
|
||||||
{
|
{
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
|
<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>
|
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
|
<br />
|
||||||
|
<Section Name="ModuleContent" Heading="Content" ResourceKey="ModuleContent">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="moduleheader" HelpText="Optionally provide content to be injected above the module instance" ResourceKey="Header">Header: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="moduleheader" class="form-control" @bind="@_header" rows="3" maxlength="4000"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="modulefooter" HelpText="Optionally provide content to be injected below the module instance" ResourceKey="Footer">Footer: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="modulefooter" class="form-control" @bind="@_footer" rows="3" maxlength="4000"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
}
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Permissions" ResourceKey="Permissions">
|
<TabPanel Name="Permissions" Heading="Permissions" ResourceKey="Permissions">
|
||||||
@if (_permissions != null)
|
@if (_permissions != null)
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||||
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" Permissions="@_permissions" @ref="_permissionGrid" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
}
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
@if (_moduleSettingsType != null)
|
@if (_moduleSettingsType != null)
|
||||||
@@ -88,29 +139,37 @@
|
|||||||
@ContainerSettingsComponent
|
@ContainerSettingsComponent
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
}
|
}
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button>
|
<br />
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button>
|
||||||
<br />
|
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
<br />
|
<br />
|
||||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
<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 List<Theme> _themes;
|
private bool _initialized = false;
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
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;
|
||||||
@@ -119,31 +178,55 @@
|
|||||||
private DateTime createdon;
|
private DateTime createdon;
|
||||||
private string modifiedby;
|
private string modifiedby;
|
||||||
private DateTime modifiedon;
|
private DateTime modifiedon;
|
||||||
|
private DateTime? _effectivedate = null;
|
||||||
|
private DateTime? _expirydate = null;
|
||||||
|
private List<Page> _pages;
|
||||||
|
private string _activetab = "";
|
||||||
|
|
||||||
protected override 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))
|
||||||
|
{
|
||||||
|
_containerType = (!string.IsNullOrEmpty(PageState.Page.DefaultContainerType)) ? PageState.Page.DefaultContainerType : PageState.Site.DefaultContainerType;
|
||||||
|
}
|
||||||
|
_header = pagemodule.Header;
|
||||||
|
_footer = pagemodule.Footer;
|
||||||
|
_effectivedate = Utilities.UtcAsLocalDate(pagemodule.EffectiveDate);
|
||||||
|
_expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate);
|
||||||
|
|
||||||
|
_allPages = pagemodule.Module.AllPages.ToString();
|
||||||
|
createdby = pagemodule.Module.CreatedBy;
|
||||||
|
createdon = pagemodule.Module.CreatedOn;
|
||||||
|
modifiedby = pagemodule.Module.ModifiedBy;
|
||||||
|
modifiedon = pagemodule.Module.ModifiedOn;
|
||||||
|
_permissions = pagemodule.Module.PermissionList;
|
||||||
|
|
||||||
|
if (pagemodule.Module.ModuleDefinition != null)
|
||||||
|
{
|
||||||
|
_module = pagemodule.Module.ModuleDefinition.Name;
|
||||||
|
_permissionNames = pagemodule.Module.ModuleDefinition?.PermissionNames;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(pagemodule.Module.ModuleDefinition.SettingsType))
|
||||||
{
|
{
|
||||||
// module settings type explicitly declared in IModule interface
|
// module settings type explicitly declared in IModule interface
|
||||||
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.SettingsType);
|
_moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.SettingsType);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
|
// legacy support - module settings type determined by convention ( ie. existence of a "Settings.razor" component in module )
|
||||||
_moduleSettingsType = Type.GetType(ModuleState.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
|
_moduleSettingsType = Type.GetType(pagemodule.Module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, PageState.Action), false, true);
|
||||||
}
|
}
|
||||||
if (_moduleSettingsType != null)
|
if (_moduleSettingsType != null)
|
||||||
{
|
{
|
||||||
@@ -156,12 +239,18 @@
|
|||||||
ModuleSettingsComponent = builder =>
|
ModuleSettingsComponent = builder =>
|
||||||
{
|
{
|
||||||
builder.OpenComponent(0, _moduleSettingsType);
|
builder.OpenComponent(0, _moduleSettingsType);
|
||||||
builder.AddComponentReferenceCapture(1, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
|
builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
|
||||||
|
builder.AddComponentReferenceCapture(2, inst => { _moduleSettings = Convert.ChangeType(inst, _moduleSettingsType); });
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(string.Format(Localizer["Error.Module.Load"], pagemodule.Module.ModuleDefinitionName), MessageType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -170,20 +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;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
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,12 +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
|
||||||
|
{
|
||||||
|
_activetab = "Settings";
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,45 +3,46 @@
|
|||||||
@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
|
||||||
|
|
||||||
<TabStrip Refresh="@_refresh">
|
@if (_initialized)
|
||||||
<TabPanel Name="Settings" ResourceKey="Settings">
|
{
|
||||||
@if (_themeList != null)
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<TabStrip Refresh="@_refresh">
|
||||||
|
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
|
||||||
<td width="30%">
|
<div class="col-sm-9">
|
||||||
<Label For="Name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
<select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="Name" class="form-control" @bind="@_name" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="Parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="Parent" class="form-select" @onchange="(e => ParentChanged(e))">
|
|
||||||
<option value="-1"><@Localizer["SiteRoot"]></option>
|
<option value="-1"><@Localizer["SiteRoot"]></option>
|
||||||
@foreach (Page page in _pageList)
|
@foreach (Page page in _pages)
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.PermissionList))
|
||||||
{
|
{
|
||||||
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
|
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<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>
|
||||||
<Label 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">
|
||||||
</td>
|
<select id="insert" class="form-select" @bind="@_insert" required>
|
||||||
<td>
|
|
||||||
<select id="Insert" class="form-select" @bind="@_insert">
|
|
||||||
<option value="<<">@Localizer["AtBeginning"]</option>
|
|
||||||
@if (_children != null && _children.Count > 0)
|
@if (_children != null && _children.Count > 0)
|
||||||
{
|
{
|
||||||
|
<option value="<<">@Localizer["AtBeginning"]</option>
|
||||||
<option value="<">@Localizer["Before"]</option>
|
<option value="<">@Localizer["Before"]</option>
|
||||||
<option value=">">@Localizer["After"]</option>
|
<option value=">">@Localizer["After"]</option>
|
||||||
}
|
}
|
||||||
@@ -57,172 +58,242 @@
|
|||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
}
|
}
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
}
|
||||||
<td>
|
else
|
||||||
<Label For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
{
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
|
||||||
<select id="navigation" class="form-select" @bind="@_isnavigation">
|
<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">
|
||||||
|
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
||||||
<Label For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="clickable" class="form-select" @bind="@_isclickable" required>
|
||||||
<td>
|
|
||||||
<select id="clickable" class="form-select" @bind="@_isclickable">
|
|
||||||
<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>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<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 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." ResourceKey="UrlPath">Url Path: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
||||||
<td>
|
</div>
|
||||||
<input id="Path" class="form-control" @bind="@_path" />
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
</tr>
|
<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>
|
||||||
<tr>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||||
<Label For="Url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
<div class="row mb-1 align-items-center">
|
||||||
<input id="Url" class="form-control" @bind="@_url" />
|
<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>
|
||||||
</td>
|
<div class="col-sm-8">
|
||||||
</tr>
|
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
||||||
</table>
|
</div>
|
||||||
<Section Name="Appearance" ResourceKey="Appearance">
|
<div class="col-sm-1">
|
||||||
<table class="table table-borderless">
|
<i class="@_icon"></i>
|
||||||
<tr>
|
</div>
|
||||||
<td width="30%">
|
</div>
|
||||||
<Label 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="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<input id="Title" class="form-control" @bind="@_title" />
|
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||||
<Label For="Theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||||
<td>
|
</div>
|
||||||
<select id="Theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))">
|
</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>
|
||||||
|
|
||||||
|
<Section Name="Appearance" ResourceKey="Appearance" Heading=@Localizer["Appearance.Name"]>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
||||||
@foreach (var theme in _themes)
|
@foreach (var theme in _themes)
|
||||||
|
{
|
||||||
|
@if (theme.TypeName == PageState.Site.DefaultThemeType)
|
||||||
|
{
|
||||||
|
<option value="@theme.TypeName">*@theme.Name*</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
<option value="@theme.TypeName">@theme.Name</option>
|
<option value="@theme.TypeName">@theme.Name</option>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
|
||||||
<Label For="defaultContainer" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="container" class="form-select" @bind="@_containertype" required>
|
||||||
<td>
|
|
||||||
<select id="defaultContainer" class="form-select" @bind="@_containertype">
|
|
||||||
<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>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>
|
</Section>
|
||||||
<Label For="Icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
<Section Name="PageContent" ResourceKey="PageContent" Heading=@Localizer["PageContent.Heading"]>
|
||||||
</td>
|
<div class="container">
|
||||||
<td>
|
<div class="row mb-1 align-items-center">
|
||||||
<input id="Icon" class="form-control" @bind="@_icon" />
|
<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>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
</tr>
|
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3" maxlength="4000"></textarea>
|
||||||
<tr>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<Label 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="row mb-1 align-items-center">
|
||||||
</td>
|
<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>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<select id="Personalizable" class="form-select" @bind="@_ispersonalizable">
|
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3" maxlength="4000"></textarea>
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
</div>
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
</div>
|
||||||
</select>
|
</div>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</Section>
|
</Section>
|
||||||
}
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Permissions" ResourceKey="Permissions">
|
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading=@Localizer["Permissions.Heading"]>
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
|
||||||
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
|
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
@if (_themeSettingsType != null)
|
</TabStrip>
|
||||||
{
|
<br />
|
||||||
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
|
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||||
@ThemeSettingsComponent
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
</TabPanel>
|
</form>
|
||||||
}
|
}
|
||||||
</TabStrip>
|
|
||||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
|
|
||||||
private List<Theme> _themeList;
|
private bool _initialized = false;
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
private List<ThemeControl> _themes = new List<ThemeControl>();
|
||||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||||
private List<Page> _pageList;
|
private List<Page> _pages;
|
||||||
|
private int _pageId;
|
||||||
private string _name;
|
private string _name;
|
||||||
private string _title;
|
private string _parentid = "-1";
|
||||||
private string _path = string.Empty;
|
|
||||||
private string _parentid;
|
|
||||||
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 _path = string.Empty;
|
||||||
private string _url;
|
private string _url;
|
||||||
private string _ispersonalizable = "False";
|
private string _ispersonalizable = "False";
|
||||||
|
private string _title;
|
||||||
|
private string _icon = string.Empty;
|
||||||
private string _themetype = string.Empty;
|
private string _themetype = string.Empty;
|
||||||
private string _containertype = string.Empty;
|
private string _containertype = string.Empty;
|
||||||
private string _icon = string.Empty;
|
private string _headcontent;
|
||||||
private string _permissions = string.Empty;
|
private string _bodycontent;
|
||||||
|
private string _permissions = null;
|
||||||
private PermissionGrid _permissionGrid;
|
private PermissionGrid _permissionGrid;
|
||||||
private Type _themeSettingsType;
|
|
||||||
private object _themeSettings;
|
|
||||||
private RenderFragment ThemeSettingsComponent { get; set; }
|
|
||||||
private bool _refresh = false;
|
private bool _refresh = false;
|
||||||
|
protected Page _parent = null;
|
||||||
|
protected 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);
|
|
||||||
|
if (PageState.QueryString.ContainsKey("id"))
|
||||||
|
{
|
||||||
|
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
||||||
|
_parent = await PageService.GetPageAsync(_pageId);
|
||||||
|
if (_parent != null)
|
||||||
|
{
|
||||||
|
_parentid = _parent.PageId.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_icons = await SystemService.GetIconsAsync();
|
||||||
|
_iconresources = typeof(IconResources).FullName;
|
||||||
|
|
||||||
|
// 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)))
|
||||||
|
{
|
||||||
_themetype = PageState.Site.DefaultThemeType;
|
_themetype = PageState.Site.DefaultThemeType;
|
||||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
_themes = ThemeService.GetThemeControls(PageState.Site.Themes);
|
||||||
|
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||||
_containertype = PageState.Site.DefaultContainerType;
|
_containertype = PageState.Site.DefaultContainerType;
|
||||||
_pageList = PageState.Pages;
|
_children = new List<Page>();
|
||||||
_children = PageState.Pages.Where(item => item.ParentId == null).ToList();
|
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||||
_permissions = string.Empty;
|
{
|
||||||
ThemeSettings();
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||||
|
{
|
||||||
|
_children.Add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Initializing Page {Error}", ex.Message);
|
await logger.LogError(ex, "Error Loading Page {Error}", ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.Page.Initialize"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Page.Load"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,102 +303,88 @@
|
|||||||
{
|
{
|
||||||
_parentid = (string)e.Value;
|
_parentid = (string)e.Value;
|
||||||
_children = new List<Page>();
|
_children = new List<Page>();
|
||||||
if (_parentid == "-1")
|
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid))))
|
||||||
{
|
{
|
||||||
foreach (Page p in PageState.Pages.Where(item => item.ParentId == null))
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||||
{
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
|
|
||||||
{
|
{
|
||||||
_children.Add(p);
|
_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();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
|
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message);
|
||||||
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.ChildPage.Load"], MessageType.Error);
|
||||||
|
await ScrollToPageTop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ThemeChanged(ChangeEventArgs e)
|
private async Task ThemeChanged(ChangeEventArgs e)
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
_themetype = (string)e.Value;
|
_themetype = (string)e.Value;
|
||||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||||
_containertype = "-";
|
_containertype = _containers.First().TypeName;
|
||||||
ThemeSettings();
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message);
|
|
||||||
AddModuleMessage(Localizer["Error.Pane.Load"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ThemeSettings()
|
// 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)
|
||||||
{
|
{
|
||||||
_themeSettingsType = null;
|
AddModuleMessage(Localizer["ThemeChanged.Message"], MessageType.Warning);
|
||||||
var theme = _themeList.FirstOrDefault(item => item.Themes.Any(themecontrol => themecontrol.TypeName.Equals(_themetype)));
|
await ScrollToPageTop();
|
||||||
if (theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
|
|
||||||
{
|
|
||||||
_themeSettingsType = Type.GetType(theme.ThemeSettingsType);
|
|
||||||
if (_themeSettingsType != null)
|
|
||||||
{
|
|
||||||
ThemeSettingsComponent = builder =>
|
|
||||||
{
|
|
||||||
builder.OpenComponent(0, _themeSettingsType);
|
|
||||||
builder.AddComponentReferenceCapture(1, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
|
|
||||||
builder.CloseComponent();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
_refresh = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SavePage()
|
private async Task SavePage()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
Page page = null;
|
Page page = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(_themetype) && _containertype != "-")
|
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(_themetype) && !string.IsNullOrEmpty(_containertype))
|
||||||
{
|
{
|
||||||
page = new Page();
|
page = new Page();
|
||||||
page.SiteId = PageState.Page.SiteId;
|
page.SiteId = PageState.Page.SiteId;
|
||||||
page.Name = _name;
|
page.Name = _name;
|
||||||
page.Title = _title;
|
|
||||||
if (_path == "")
|
if (_parentid == "-1")
|
||||||
|
{
|
||||||
|
page.ParentId = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
page.ParentId = Int32.Parse(_parentid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_path))
|
||||||
{
|
{
|
||||||
_path = _name;
|
_path = _name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_path.Contains("/"))
|
if (_path.Contains("/"))
|
||||||
{
|
{
|
||||||
|
if (_path.EndsWith("/") && _path != "/")
|
||||||
|
{
|
||||||
|
_path = _path.Substring(0, _path.Length - 1);
|
||||||
|
}
|
||||||
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
_path = _path.Substring(_path.LastIndexOf("/") + 1);
|
||||||
}
|
}
|
||||||
|
if (_parentid == "-1")
|
||||||
if (string.IsNullOrEmpty(_parentid))
|
|
||||||
{
|
{
|
||||||
page.ParentId = null;
|
|
||||||
page.Path = Utilities.GetFriendlyUrl(_path);
|
page.Path = Utilities.GetFriendlyUrl(_path);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
page.ParentId = Int32.Parse(_parentid);
|
Page parent = _pages.FirstOrDefault(item => item.PageId == page.ParentId);
|
||||||
var parent = PageState.Pages.Where(item => item.PageId == page.ParentId).FirstOrDefault();
|
|
||||||
if (parent.Path == string.Empty)
|
if (parent.Path == string.Empty)
|
||||||
{
|
{
|
||||||
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
|
||||||
@@ -338,9 +395,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PagePathIsUnique(page.Path, page.SiteId, _pageList))
|
if (_pages.Any(item => item.Path == page.Path))
|
||||||
{
|
{
|
||||||
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
|
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page.ParentId == null && Constants.ReservedRoutes.Contains(page.Name.ToLower()))
|
||||||
|
{
|
||||||
|
AddModuleMessage(string.Format(Localizer["Message.Page.Reserved"], page.Name), MessageType.Warning);
|
||||||
|
await ScrollToPageTop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,11 +416,11 @@
|
|||||||
page.Order = 0;
|
page.Order = 0;
|
||||||
break;
|
break;
|
||||||
case "<":
|
case "<":
|
||||||
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
|
child = _pages.Where(item => item.PageId == _childid).FirstOrDefault();
|
||||||
page.Order = child.Order - 1;
|
page.Order = child.Order - 1;
|
||||||
break;
|
break;
|
||||||
case ">":
|
case ">":
|
||||||
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
|
child = _pages.Where(item => item.PageId == _childid).FirstOrDefault();
|
||||||
page.Order = child.Order + 1;
|
page.Order = child.Order + 1;
|
||||||
break;
|
break;
|
||||||
case ">>":
|
case ">>":
|
||||||
@@ -366,37 +431,49 @@
|
|||||||
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
|
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
|
||||||
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
|
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
|
||||||
page.Url = _url;
|
page.Url = _url;
|
||||||
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
|
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)
|
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
|
||||||
{
|
{
|
||||||
page.ThemeType = string.Empty;
|
page.ThemeType = string.Empty;
|
||||||
}
|
}
|
||||||
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
|
page.DefaultContainerType = _containertype;
|
||||||
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
|
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
|
||||||
{
|
{
|
||||||
page.DefaultContainerType = string.Empty;
|
page.DefaultContainerType = string.Empty;
|
||||||
}
|
}
|
||||||
page.Icon = (_icon == null ? string.Empty : _icon);
|
|
||||||
page.Permissions = _permissionGrid.GetPermissions();
|
// page content
|
||||||
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
|
page.HeadContent = _headcontent;
|
||||||
page.UserId = null;
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -404,23 +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
|
||||||
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,19 +5,21 @@
|
|||||||
@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>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
<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>@(new string('-', context.Level * 2))@(context.Name)</td>
|
<td>@(new string('-', context.Level * 2))@(context.Name)</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@@ -26,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
|
||||||
@@ -42,4 +59,8 @@
|
|||||||
AddModuleMessage(Localizer["Error.Page.Delete"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Page.Delete"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
protected string Browse(Page page)
|
||||||
|
{
|
||||||
|
return string.IsNullOrEmpty(page.Url) ? NavigateUrl(page.Path) : page.Url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,113 +5,119 @@
|
|||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<table class="table table-borderless">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<tr>
|
<div class="container">
|
||||||
<td width="30%">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label For="name" HelpText="The name of this profile item" ResourceKey="Name">Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="The name of this profile item" ResourceKey="Name">Name: </Label>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
||||||
<input id="name" class="form-control" @bind="@_name" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="title" HelpText="The title of the profile item to display to the user" ResourceKey="Title">Title: </Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="title" HelpText="The title of the profile item to display to the user" ResourceKey="Title">Title: </Label>
|
<input id="title" class="form-control" @bind="@_title" maxlength="50" required />
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<input id="title" class="form-control" @bind="@_title" />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="description" HelpText="The help text displayed to the user for this profile item" ResourceKey="Description">Description: </Label>
|
||||||
</tr>
|
<div class="col-sm-9">
|
||||||
<tr>
|
<textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="256" required></textarea>
|
||||||
<td>
|
</div>
|
||||||
<Label For="description" HelpText="The help text displayed to the user for this profile item" ResourceKey="Description">Description: </Label>
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="category" HelpText="The category of this profile item (for grouping)" ResourceKey="Category">Category: </Label>
|
||||||
<textarea id="description" class="form-control" @bind="@_description" rows="5"></textarea>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="category" class="form-control" @bind="@_category" maxlength="50" />
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>
|
<div class="row mb-1 align-items-center">
|
||||||
<Label For="category" HelpText="The category of this profile item (for grouping)" ResourceKey="Category">Category: </Label>
|
<Label Class="col-sm-3" For="order" HelpText="The index order of where this profile item should be displayed" ResourceKey="Order">Order: </Label>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="order" class="form-control" @bind="@_vieworder" min="0" max="9999" type="number" required />
|
||||||
<input id="category" class="form-control" @bind="@_category" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<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>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="order" HelpText="The index order of where this profile item should be displayed" ResourceKey="Order">Order: </Label>
|
<input id="length" class="form-control" @bind="@_maxlength" min="0" max="524288" type="number" required />
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<input id="order" class="form-control" @bind="@_vieworder" />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="rows" HelpText="The number of rows for text entry (one is the default)" ResourceKey="Rows">Rows: </Label>
|
||||||
</tr>
|
<div class="col-sm-9">
|
||||||
<tr>
|
<input id="rows" class="form-control" @bind="@_rows" min="1" max="10" type="number" required />
|
||||||
<td>
|
</div>
|
||||||
<Label For="length" HelpText="The max number of characters this profile item should accept (enter zero for unlimited)" ResourceKey="Length">Length: </Label>
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="defaultVal" HelpText="The default value for this profile item" ResourceKey="DefaultValue">Default Value: </Label>
|
||||||
<input id="length" class="form-control" @bind="@_maxlength" />
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="defaultVal" class="form-control" @bind="@_defaultvalue" maxlength="2000" />
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>
|
<div class="row mb-1 align-items-center">
|
||||||
<Label For="defaultVal" HelpText="The default value for this profile item" ResourceKey="DefaultValue">Default Value: </Label>
|
<Label Class="col-sm-3" For="options" HelpText="A comma delimited list of options the user can select from" ResourceKey="Options">Options: </Label>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="options" class="form-control" @bind="@_options" maxlength="2000" />
|
||||||
<input id="defaultVal" class="form-control" @bind="@_defaultvalue" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="required" HelpText="Should a user be required to provide a value for this profile item?" ResourceKey="Required">Required? </Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="options" HelpText="A comma delimited list of options the user can select from" ResourceKey="Options">Options: </Label>
|
<select id="required" class="form-select" @bind="@_isrequired" required>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="options" class="form-control" @bind="@_options" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="required" HelpText="Should a user be required to provide a value for this profile item?" ResourceKey="Required">Required? </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="required" class="form-select" @bind="@_isrequired">
|
|
||||||
<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>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="validation" HelpText="Optionally provide a regular expression (RegExp) for validating the value entered" ResourceKey="Validation">Validation: </Label>
|
||||||
<Label For="private" HelpText="Should this profile item be visible to all users?" ResourceKey="Private">Private? </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="validation" class="form-control" @bind="@_validation" maxlength="200" />
|
||||||
<td>
|
</div>
|
||||||
<select id="private" class="form-select" @bind="@_isprivate">
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="autocomplete" HelpText="The HTML autocomplete attribute allows you to specify browser behavior for automated user assistance in filling out form field values. Allowable values are blank (default), 'on', 'off', or any value from the standardized taxonomy defined for this attribute." ResourceKey="Autocomplete">Autocomplete: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="autocomplete" class="form-control" @bind="@_autocomplete" maxlength="30" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="private" HelpText="Should this profile item be visible to all users?" ResourceKey="Private">Private? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="private" class="form-select" @bind="@_isprivate" 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>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveProfile">@SharedLocalizer["Save"]</button>
|
<br />
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<button type="button" class="btn btn-success" @onclick="SaveProfile">@SharedLocalizer["Save"]</button>
|
||||||
@if (PageState.QueryString.ContainsKey("id"))
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
{
|
@if (PageState.QueryString.ContainsKey("id"))
|
||||||
|
{
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
||||||
}
|
}
|
||||||
|
</form>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private int _profileid = -1;
|
private int _profileid = -1;
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
private string _name = string.Empty;
|
private string _name = string.Empty;
|
||||||
private string _title = string.Empty;
|
private string _title = string.Empty;
|
||||||
private string _description = string.Empty;
|
private string _description = string.Empty;
|
||||||
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;
|
||||||
@@ -119,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";
|
||||||
|
|
||||||
@@ -139,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;
|
||||||
@@ -158,6 +167,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveProfile()
|
private async Task SaveProfile()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -178,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);
|
||||||
@@ -200,4 +217,9 @@
|
|||||||
AddModuleMessage(Localizer["Error.Profile.Save"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Profile.Save"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,48 +7,50 @@
|
|||||||
@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>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<TabStrip>
|
||||||
|
<TabPanel Name="Pages" ResourceKey="Pages" Heading="Pages">
|
||||||
|
@if (!_pages.Where(item => item.IsDeleted).Any())
|
||||||
{
|
{
|
||||||
<br />
|
<br />
|
||||||
<p>@Localizer["NoPage.Deleted"]</p>
|
<p>@Localizer["NoPage.Deleted"]</p>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<Pager Items="@_pages">
|
<Pager Items="@_pages.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Path"]</th>
|
||||||
<th>@Localizer["DeletedBy"]</th>
|
<th>@Localizer["DeletedBy"]</th>
|
||||||
<th>@Localizer["DeletedOn"]</th>
|
<th>@Localizer["DeletedOn"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><button @onclick="@(() => RestorePage(context))" class="btn btn-info" title="Restore">Restore</button></td>
|
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
||||||
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Path</td>
|
||||||
<td>@context.DeletedBy</td>
|
<td>@context.DeletedBy</td>
|
||||||
<td>@context.DeletedOn</td>
|
<td>@context.DeletedOn</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@if (_pages.Any())
|
<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" />
|
||||||
<div style="text-align:right;">
|
|
||||||
<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" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Modules" ResourceKey="Modules">
|
<TabPanel Name="Modules" ResourceKey="Modules" Heading="Modules">
|
||||||
@if (_modules == null)
|
@if (!_modules.Where(item => item.IsDeleted).Any())
|
||||||
{
|
{
|
||||||
<br />
|
<br />
|
||||||
<p>@Localizer["NoModule.Deleted"]</p>
|
<p>@Localizer["NoModule.Deleted"]</p>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<Pager Items="@_modules">
|
<Pager Items="@_modules.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -58,29 +60,26 @@
|
|||||||
<th>@Localizer["DeletedOn"]</th>
|
<th>@Localizer["DeletedOn"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><button @onclick="@(() => RestoreModule(context))" class="btn btn-info" title="Restore">@Localizer["Restore"]</button></td>
|
<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><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>@_pages.Find(item => item.PageId == context.PageId).Name</td>
|
||||||
<td>@context.Title</td>
|
<td>@context.Title</td>
|
||||||
<td>@context.DeletedBy</td>
|
<td>@context.DeletedBy</td>
|
||||||
<td>@context.DeletedOn</td>
|
<td>@context.DeletedOn</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@if (_modules.Any())
|
<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" />
|
||||||
<div style="text-align:right;">
|
|
||||||
<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" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</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()
|
||||||
@@ -99,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
|
||||||
|
{
|
||||||
|
var validated = true;
|
||||||
|
if (page.ParentId != null)
|
||||||
|
{
|
||||||
|
var parent = _pages.Find(item => item.PageId == page.ParentId);
|
||||||
|
validated = !parent.IsDeleted;
|
||||||
|
}
|
||||||
|
if (validated)
|
||||||
{
|
{
|
||||||
page.IsDeleted = false;
|
page.IsDeleted = false;
|
||||||
await PageService.UpdatePageAsync(page);
|
await PageService.UpdatePageAsync(page);
|
||||||
await logger.LogInformation("Page Restored {Page}", page);
|
await logger.LogInformation("Page Restored {Page}", page);
|
||||||
|
AddModuleMessage(Localizer["Success.Page.Restore"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Page.Restore"], MessageType.Warning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -129,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)
|
||||||
{
|
{
|
||||||
@@ -144,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,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();
|
||||||
}
|
}
|
||||||
@@ -185,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();
|
||||||
}
|
}
|
||||||
@@ -208,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,91 +1,141 @@
|
|||||||
@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" />
|
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
|
||||||
</Authorized>
|
}
|
||||||
<NotAuthorized>
|
else
|
||||||
<ModuleMessage Message="@Localizer["Info.Registration.InvalidEmail"]" Type="MessageType.Info" />
|
{
|
||||||
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
<table class="table table-borderless">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<tr>
|
<div class="container">
|
||||||
<td width="30%">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
|
<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>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="username" class="form-control" @bind="@_username" maxlength="256" required />
|
||||||
<input id="username" class="form-control" @bind="@_username" readonly />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label 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="input-group">
|
||||||
</td>
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
||||||
<td>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
<input id="password" type="password" class="form-control" @bind="@_password" autocomplete="new-password" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
|
||||||
<Label 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">
|
||||||
</td>
|
<div class="input-group">
|
||||||
<td>
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
||||||
<input id="confirm" type="password" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>
|
<div class="row mb-1 align-items-center">
|
||||||
<Label 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>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
|
||||||
<input id="email" class="form-control" @bind="@_email" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<input id="displayname" class="form-control" @bind="@_displayname" />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||||
</tr>
|
<div class="col-sm-9">
|
||||||
</table>
|
<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>
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
</NotAuthorized>
|
@if (_allowsitelogin)
|
||||||
</AuthorizeView>
|
{
|
||||||
}
|
<br />
|
||||||
else
|
|
||||||
{
|
<br />
|
||||||
|
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
<ModuleMessage Message="@Localizer["Info.Registration.Disabled"]" Type="MessageType.Info" />
|
<ModuleMessage Message="@Localizer["Info.Registration.Disabled"]" Type="MessageType.Info" />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private bool _initialized = false;
|
||||||
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private string _passwordrequirements;
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
private string _password = string.Empty;
|
private string _password = string.Empty;
|
||||||
|
private string _passwordtype = "password";
|
||||||
|
private string _togglepassword = string.Empty;
|
||||||
private string _confirm = string.Empty;
|
private string _confirm = string.Empty;
|
||||||
private string _email = string.Empty;
|
private string _email = string.Empty;
|
||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
|
private string _timezoneid = string.Empty;
|
||||||
|
private bool _userCreated = false;
|
||||||
|
private bool _allowsitelogin = true;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_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()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
bool _isEmailValid = Utilities.IsValidEmail(_email);
|
bool _isEmailValid = Utilities.IsValidEmail(_email);
|
||||||
|
|
||||||
if (_username != "" && _password != "" && _confirm != "" && _isEmailValid)
|
if (_isEmailValid)
|
||||||
{
|
{
|
||||||
if (_password == _confirm)
|
if (_password == _confirm)
|
||||||
{
|
{
|
||||||
@@ -93,15 +143,18 @@ else
|
|||||||
{
|
{
|
||||||
SiteId = PageState.Site.SiteId,
|
SiteId = PageState.Site.SiteId,
|
||||||
Username = _username,
|
Username = _username,
|
||||||
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
Password = _password,
|
||||||
Email = _email,
|
Email = _email,
|
||||||
Password = _password
|
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
||||||
|
TimeZoneId = _timezoneid,
|
||||||
|
PhotoFileId = null
|
||||||
};
|
};
|
||||||
user = await UserService.AddUserAsync(user);
|
user = await UserService.AddUserAsync(user);
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await logger.LogInformation("User Created {Username} {Email}", _username, _email);
|
await logger.LogInformation("User Created {Username} {Email}", _username, _email);
|
||||||
|
_userCreated = true;
|
||||||
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
|
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -126,9 +179,28 @@ else
|
|||||||
AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Cancel()
|
private void Cancel()
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordtype == "password")
|
||||||
|
{
|
||||||
|
_passwordtype = "text";
|
||||||
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordtype = "password";
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,43 +5,72 @@
|
|||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<div class="container">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<div class="form-group">
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
<label for="Username" class="control-label">@SharedLocalizer["Username"] </label>
|
<div class="container">
|
||||||
<input type="text" class="form-control" placeholder="Username" @bind="@_username" readonly id="Username" />
|
<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>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="username" type="text" class="form-control" @bind="@_username" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label for="Password" class="control-label">@SharedLocalizer["Password"] </label>
|
|
||||||
<input type="password" class="form-control" placeholder="Password" @bind="@_password" id="Password" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="row mb-1 align-items-center">
|
||||||
<label for="Confirm" class="control-label">@Localizer["Password.Confirm"] </label>
|
<Label Class="col-sm-3" For="password" HelpText="The new password. It must satisfy complexity rules for the site." ResourceKey="Password">Password: </Label>
|
||||||
<input type="password" class="form-control" placeholder="Password" @bind="@_confirm" id="Confirm" />
|
<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>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="confirm" HelpText="Enter the password again. It must exactly match the password entered above." ResourceKey="Confirm">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>
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-primary" @onclick="Reset">@Localizer["Password.Reset"]</button>
|
<button type="button" class="btn btn-primary" @onclick="Reset">@Localizer["Password.Reset"]</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>
|
||||||
</div>
|
</form>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
private string _username = string.Empty;
|
private string _username = string.Empty;
|
||||||
private string _password = string.Empty;
|
private string _password = string.Empty;
|
||||||
|
private string _passwordtype = "password";
|
||||||
|
private string _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 void OnInitialized()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
_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"))
|
||||||
{
|
{
|
||||||
_username = PageState.QueryString["name"];
|
_username = PageState.QueryString["name"];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
|
await logger.LogError(LogFunction.Security, "Invalid Attempt To Access User Password Reset");
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl("")); // home page
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Reset()
|
private async Task Reset()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -84,9 +113,28 @@
|
|||||||
AddModuleMessage(Localizer["Error.Password.Reset"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Password.Reset"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Cancel()
|
private void Cancel()
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
|
NavigationManager.NavigateTo(NavigateUrl(string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordtype == "password")
|
||||||
|
{
|
||||||
|
_passwordtype = "text";
|
||||||
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordtype = "password";
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,37 +6,32 @@
|
|||||||
@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>
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="name" HelpText="Name Of The Role" ResourceKey="Name">Name:</Label>
|
||||||
<Label For="name" HelpText="Name Of The Role" ResourceKey="Name">Name:</Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="description" HelpText="A Short Description Of The Role Which Describes Its Purpose" ResourceKey="Description">Description:</Label>
|
||||||
<Label For="description" HelpText="A Short Description Of The Role Which Describes Its Purpose" ResourceKey="Description">Description:</Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<textarea id="description" class="form-control" @bind="@_description" rows="5" maxlength="256" required></textarea>
|
<textarea id="description" class="form-control" @bind="@_description" rows="5" maxlength="256" required></textarea>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="isautoassigned" HelpText="Indicates Whether Or Not New Users Are Automatically Assigned To This Role" ResourceKey="AutoAssigned">Auto Assigned?</Label>
|
||||||
<Label For="isautoassigned" HelpText="Indicates Whether Or Not New Users Are Automatically Assigned To This Role" ResourceKey="AutoAssigned">Auto Assigned?</Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="isautoassigned" class="form-select" @bind="@_isautoassigned" required>
|
||||||
<td>
|
|
||||||
<select id="isautoassigned" class="form-select" @bind="@_isautoassigned">
|
|
||||||
<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>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
<br /><br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveRole">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveRole">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -47,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()
|
||||||
{
|
{
|
||||||
@@ -77,7 +72,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,39 +6,34 @@
|
|||||||
@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>
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="name" HelpText="Name Of The Role" ResourceKey="Name">Name:</Label>
|
||||||
<Label For="name" HelpText="Name Of The Role" ResourceKey="Name">Name:</Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="description" HelpText="A Short Description Of The Role Which Describes Its Purpose" ResourceKey="Description">Description:</Label>
|
||||||
<Label For="description" HelpText="A Short Description Of The Role Which Describes Its Purpose" ResourceKey="Description">Description:</Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<textarea id="description" class="form-control" @bind="@_description" rows="5" maxlength="256" required></textarea>
|
<textarea id="description" class="form-control" @bind="@_description" rows="5" maxlength="256" required></textarea>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="isautoassigned" HelpText="Indicates Whether Or Not New Users Are Automatically Assigned To This Role" ResourceKey="AutoAssigned">Auto Assigned?</Label>
|
||||||
<Label For="isautoassigned" HelpText="Indicates Whether Or Not New Users Are Automatically Assigned To This Role" ResourceKey="AutoAssigned">Auto Assigned?</Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="isautoassigned" class="form-select" @bind="@_isautoassigned" required>
|
||||||
<td>
|
|
||||||
<select id="isautoassigned" class="form-select" @bind="@_isautoassigned">
|
|
||||||
<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>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
<br /><br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveRole">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveRole">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon"></AuditInfo>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -54,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()
|
||||||
{
|
{
|
||||||
@@ -106,7 +101,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,80 +11,75 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
|
||||||
<tr>
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<td width="30%">
|
<div class="container">
|
||||||
<Label For="role" HelpText="The role you are assigning users to" ResourceKey="Role">Role: </Label>
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="role" HelpText="The role you are assigning users to" ResourceKey="Role">Role: </Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<input id="role" class="form-control" @bind="@name" disabled />
|
<input id="role" class="form-control" @bind="@name" disabled />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="user" HelpText="Select a user" ResourceKey="User">User: </Label>
|
||||||
<Label For="user" HelpText="Select a user" ResourceKey="User">User: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["User.Select"]" @ref="user" />
|
||||||
<td>
|
</div>
|
||||||
<select id="user" class="form-select" @bind="@userid">
|
</div>
|
||||||
<option value="-1"><@Localizer["User.Select"]></option>
|
<div class="row mb-1 align-items-center">
|
||||||
@foreach (UserRole userrole in users)
|
<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">
|
||||||
<option value="@(userrole.UserId)">@userrole.User.DisplayName</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="date" id="effectiveDate" class="form-control" @bind="@effectivedate" />
|
<input type="date" id="effectiveDate" class="form-control" @bind="@effectivedate" />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||||
<Label For="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input type="date" id="expiryDate" class="form-control" @bind="@expirydate" />
|
<input type="date" id="expiryDate" class="form-control" @bind="@expirydate" />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
<br /><br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveUserRole">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveUserRole">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
|
||||||
<hr class="app-rule" />
|
<hr class="app-rule" />
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<Pager Items="@userroles">
|
<Pager Items="@userroles">
|
||||||
<Header>
|
<Header>
|
||||||
<th>@Localizer["Users"]</th>
|
<th>@Localizer["Users"]</th>
|
||||||
|
<th>@SharedLocalizer["Username"]</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.User.DisplayName</td>
|
<td>@context.User.DisplayName</td>
|
||||||
|
<td>@context.User.Username</td>
|
||||||
<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>
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private ElementReference form;
|
||||||
|
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()
|
||||||
{
|
{
|
||||||
@@ -93,11 +88,6 @@ else
|
|||||||
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);
|
|
||||||
users = users
|
|
||||||
.Where(u => u.Role.Name == RoleNames.Registered)
|
|
||||||
.OrderBy(u => u.User.DisplayName)
|
|
||||||
.ToList();
|
|
||||||
await GetUserRoles();
|
await GetUserRoles();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -107,12 +97,27 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<Dictionary<string, string>> GetUsers(string filter)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||||
|
return users.Where(item => item.User.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToDictionary(item => item.UserId.ToString(), item => item.User.DisplayName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
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 GetUserRoles()
|
private async Task GetUserRoles()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
|
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, name);
|
||||||
userroles = userroles.Where(item => item.RoleId == roleid).ToList();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -122,10 +127,14 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveUserRole()
|
private async Task SaveUserRole()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (userid != -1)
|
if (!string.IsNullOrEmpty(user.Key) && int.TryParse(user.Key, out int userid))
|
||||||
{
|
{
|
||||||
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)
|
||||||
@@ -148,6 +157,7 @@ else
|
|||||||
await logger.LogInformation("User Assigned To Role {UserRole}", userrole);
|
await logger.LogInformation("User Assigned To Role {UserRole}", userrole);
|
||||||
AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success);
|
||||||
await GetUserRoles();
|
await GetUserRoles();
|
||||||
|
user.Clear();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -162,12 +172,28 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task DeleteUserRole(int UserRoleId)
|
private async Task DeleteUserRole(int UserRoleId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
var userrole = await UserRoleService.GetUserRoleAsync(UserRoleId);
|
||||||
|
if (userrole.Role.Name == RoleNames.Registered)
|
||||||
|
{
|
||||||
|
userrole.ExpiryDate = DateTime.UtcNow;
|
||||||
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
||||||
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
|
await logger.LogInformation("User {Username} Removed From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success);
|
AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success);
|
||||||
await GetUserRoles();
|
await GetUserRoles();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|||||||
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
@@ -1,5 +1,6 @@
|
|||||||
@namespace Oqtane.Modules.Admin.Sites
|
@namespace Oqtane.Modules.Admin.Sites
|
||||||
@using Oqtane.Interfaces
|
@using Oqtane.Interfaces
|
||||||
|
@using System.Text.RegularExpressions
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject ITenantService TenantService
|
@inject ITenantService TenantService
|
||||||
@@ -19,86 +20,80 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
<tr>
|
<div class="container">
|
||||||
<td width="30%">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label For="name" HelpText="Enter the name of the site" ResourceKey="Name">Site Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="Enter the name of the site" ResourceKey="Name">Site Name: </Label>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
|
||||||
<input id="name" class="form-control" @bind="@_name" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<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>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label 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>
|
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<textarea id="alias" class="form-control" @bind="@_urls" rows="3"></textarea>
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label>
|
||||||
</tr>
|
<div class="col-sm-9">
|
||||||
<tr>
|
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
||||||
<td>
|
|
||||||
<Label For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="defaultTheme" class="form-select" @onchange="(e => ThemeChanged(e))">
|
|
||||||
<option value="-"><@Localizer["Theme.Select"]></option>
|
<option value="-"><@Localizer["Theme.Select"]></option>
|
||||||
@foreach (var theme in _themes)
|
@foreach (var theme in _themes)
|
||||||
{
|
{
|
||||||
<option value="@theme.TypeName">@theme.Name</option>
|
<option value="@theme.TypeName">@theme.Name</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
|
||||||
<Label For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
|
||||||
<td>
|
|
||||||
<select id="defaultContainer" class="form-select" @bind="@_containertype">
|
|
||||||
<option value="-"><@Localizer["Container.Select"]></option>
|
<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>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label>
|
||||||
<Label For="adminContainer" HelpText="Select the admin container for the site" ResourceKey="AdminContainer">Admin Container: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="siteTemplate" class="form-select" @bind="@_sitetemplatetype" required>
|
||||||
<td>
|
|
||||||
<select id="adminContainer" class="form-select" @bind="@_admincontainertype">
|
|
||||||
<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>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="siteTemplate" class="form-select" @bind="@_sitetemplatetype">
|
|
||||||
<option value="-"><@Localizer["SiteTemplate.Select"]></option>
|
<option value="-"><@Localizer["SiteTemplate.Select"]></option>
|
||||||
@foreach (SiteTemplate siteTemplate in _siteTemplates)
|
@foreach (SiteTemplate siteTemplate in _siteTemplates)
|
||||||
{
|
{
|
||||||
<option value="@siteTemplate.TypeName">@siteTemplate.Name</option>
|
<option value="@siteTemplate.TypeName">@siteTemplate.Name</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="rendermode" HelpText="The default render mode for the site" ResourceKey="Rendermode">Render Mode: </Label>
|
||||||
<Label For="tenant" HelpText="Select the tenant for the site" ResourceKey="Tenant">Tenant: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="rendermode" class="form-select" @bind="@_rendermode" required>
|
||||||
<td>
|
<option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
|
||||||
<select id="tenant" class="form-select" @onchange="(e => TenantChanged(e))">
|
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
|
||||||
|
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="runtime" HelpText="The render mode for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
||||||
|
<option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
|
||||||
|
<option value="@Runtimes.WebAssembly">@(SharedLocalizer["Runtime" + @Runtimes.WebAssembly])</option>
|
||||||
|
<option value="@Runtimes.Auto">@(SharedLocalizer["Runtime" + @Runtimes.Auto])</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="tenant" HelpText="Select the database for the site" ResourceKey="Tenant">Database: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="tenant" class="form-select" value="@_tenantid" @onchange="(e => TenantChanged(e))" required>
|
||||||
<option value="-"><@Localizer["Tenant.Select"]></option>
|
<option value="-"><@Localizer["Tenant.Select"]></option>
|
||||||
<option value="+"><@Localizer["Tenant.Add"]></option>
|
<option value="+"><@Localizer["Tenant.Add"]></option>
|
||||||
@foreach (Tenant tenant in _tenants)
|
@foreach (Tenant tenant in _tenants)
|
||||||
@@ -106,76 +101,90 @@ else
|
|||||||
<option value="@tenant.TenantId">@tenant.Name</option>
|
<option value="@tenant.TenantId">@tenant.Name</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
@if (_tenantid == "+")
|
@if (_tenantid == "+")
|
||||||
{
|
{
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td colspan="2">
|
|
||||||
<hr class="app-rule" />
|
<hr class="app-rule" />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<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>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="name" HelpText="Enter the name for the tenant" ResourceKey="TenantName">Tenant Name: </Label>
|
<input id="name" class="form-control" @bind="@_tenantName" maxlength="100" required />
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<input id="name" class="form-control" @bind="@_tenantName" />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="databaseType" HelpText="Select the database type" ResourceKey="DatabaseType">Type: </Label>
|
||||||
</tr>
|
<div class="col-sm-9">
|
||||||
<tr>
|
@if (_databases != null)
|
||||||
<td>
|
{
|
||||||
<Label For="databaseType" HelpText="Select the database type for the tenant" ResourceKey="DatabaseType">Database Type: </Label>
|
<div class="input-group">
|
||||||
</td>
|
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
|
||||||
<td>
|
|
||||||
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))">
|
|
||||||
@foreach (var database in _databases)
|
@foreach (var database in _databases)
|
||||||
{
|
|
||||||
if (database.IsDefault)
|
|
||||||
{
|
|
||||||
<option value="@database.Name" selected>@Localizer[@database.Name]</option>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
<option value="@database.Name">@Localizer[@database.Name]</option>
|
<option value="@database.Name">@Localizer[@database.Name]</option>
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</select>
|
</select>
|
||||||
</td>
|
@if (!_showConnectionString)
|
||||||
</tr>
|
{
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionString"]</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="ToggleConnectionString">@Localizer["EnterConnectionParameters"]</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (!_showConnectionString)
|
||||||
|
{
|
||||||
if (_databaseConfigType != null)
|
if (_databaseConfigType != null)
|
||||||
{
|
{
|
||||||
@DatabaseConfigComponent;
|
@DatabaseConfigComponent
|
||||||
}
|
}
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="hostUsername" HelpText="Enter the username of the host for this site" ResourceKey="HostUsername">Host Username:</Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="hostUsername" class="form-control" @bind="@_hostUserName" readonly />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="hostPassword" HelpText="Enter the password for the host of this site" ResourceKey="HostPassword">Host Password:</Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="hostPassword" type="password" class="form-control" @bind="@_hostpassword" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
}
|
||||||
</table>
|
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">
|
||||||
|
<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">
|
||||||
|
<input id="hostUsername" class="form-control" @bind="@_hostusername" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="hostPassword" HelpText="Enter the password of an existing host user" ResourceKey="HostPassword">Host Password:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="hostPassword" type="password" class="form-control" @bind="@_hostpassword" autocomplete="new-password" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveSite">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveSite">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</form>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Database> _databases;
|
private List<Database> _databases;
|
||||||
private string _databaseName = "LocalDB";
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
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<Theme> _themeList;
|
||||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
private List<ThemeControl> _themes = new List<ThemeControl>();
|
||||||
@@ -186,26 +195,50 @@ else
|
|||||||
|
|
||||||
private string _tenantName = string.Empty;
|
private string _tenantName = string.Empty;
|
||||||
|
|
||||||
private string _hostUserName = UserNames.Host;
|
private string _hostusername = string.Empty;
|
||||||
private string _hostpassword = string.Empty;
|
private string _hostpassword = string.Empty;
|
||||||
|
|
||||||
private string _name = string.Empty;
|
private string _name = string.Empty;
|
||||||
private string _urls = string.Empty;
|
private string _urls = string.Empty;
|
||||||
private string _themetype = "-";
|
private string _themetype = "-";
|
||||||
private string _containertype = "-";
|
private string _containertype = "-";
|
||||||
private string _admincontainertype = "";
|
|
||||||
private string _sitetemplatetype = "-";
|
private string _sitetemplatetype = "-";
|
||||||
|
private string _rendermode = RenderModes.Static;
|
||||||
|
private string _runtime = Runtimes.Server;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_tenants = await TenantService.GetTenantsAsync();
|
_tenants = await TenantService.GetTenantsAsync();
|
||||||
|
if (_tenants.Any(item => item.Name == TenantNames.Master))
|
||||||
|
{
|
||||||
|
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
|
||||||
|
}
|
||||||
_urls = PageState.Alias.Name;
|
_urls = PageState.Alias.Name;
|
||||||
_themeList = await ThemeService.GetThemesAsync();
|
_themeList = await ThemeService.GetThemesAsync();
|
||||||
_themes = ThemeService.GetThemeControls(_themeList);
|
_themes = ThemeService.GetThemeControls(_themeList);
|
||||||
|
if (_themes.Any(item => item.TypeName == Constants.DefaultTheme))
|
||||||
|
{
|
||||||
|
_themetype = Constants.DefaultTheme;
|
||||||
|
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
||||||
|
_containertype = _containers.First().TypeName;
|
||||||
|
}
|
||||||
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
|
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
|
||||||
|
if (_siteTemplates.Any(item => item.TypeName == Constants.DefaultSiteTemplate))
|
||||||
|
{
|
||||||
|
_sitetemplatetype = Constants.DefaultSiteTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
_databases = await DatabaseService.GetDatabasesAsync();
|
_databases = await DatabaseService.GetDatabasesAsync();
|
||||||
|
if (_databases.Exists(item => item.IsDefault))
|
||||||
|
{
|
||||||
|
_databaseName = _databases.Find(item => item.IsDefault).Name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_databaseName = "LocalDB";
|
||||||
|
}
|
||||||
LoadDatabaseConfigComponent();
|
LoadDatabaseConfigComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +247,7 @@ else
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_databaseName = (string)eventArgs.Value;
|
_databaseName = (string)eventArgs.Value;
|
||||||
|
_showConnectionString = false;
|
||||||
LoadDatabaseConfigComponent();
|
LoadDatabaseConfigComponent();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -256,13 +289,13 @@ else
|
|||||||
if (_themetype != "-")
|
if (_themetype != "-")
|
||||||
{
|
{
|
||||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
||||||
|
_containertype = _containers.First().TypeName;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_containers = new List<ThemeControl>();
|
_containers = new List<ThemeControl>();
|
||||||
}
|
|
||||||
_containertype = "-";
|
_containertype = "-";
|
||||||
_admincontainertype = "";
|
}
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -273,12 +306,17 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task SaveSite()
|
private async Task SaveSite()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
|
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
|
||||||
{
|
{
|
||||||
|
_urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
|
||||||
var duplicates = new List<string>();
|
var duplicates = new List<string>();
|
||||||
var aliases = await AliasService.GetAliasesAsync();
|
var aliases = await AliasService.GetAliasesAsync();
|
||||||
foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
foreach (string name in _urls.Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
if (aliases.Exists(item => item.Name == name))
|
if (aliases.Exists(item => item.Name == name))
|
||||||
{
|
{
|
||||||
@@ -292,30 +330,39 @@ else
|
|||||||
|
|
||||||
if (_tenantid == "+")
|
if (_tenantid == "+")
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(_tenantName) && _tenants.FirstOrDefault(item => item.Name == _tenantName) == null)
|
if (!string.IsNullOrEmpty(_tenantName) && !_tenants.Exists(item => item.Name == _tenantName))
|
||||||
{
|
{
|
||||||
// validate host credentials
|
// validate host credentials
|
||||||
var user = new User();
|
var user = new User();
|
||||||
user.SiteId = PageState.Site.SiteId;
|
user.SiteId = PageState.Site.SiteId;
|
||||||
user.Username = UserNames.Host;
|
user.Username = _hostusername;
|
||||||
user.Password = _hostpassword;
|
user.Password = _hostpassword;
|
||||||
|
user.LastIPAddress = PageState.RemoteIPAddress;
|
||||||
user = await UserService.LoginUserAsync(user, false, false);
|
user = await UserService.LoginUserAsync(user, false, false);
|
||||||
if (user.IsAuthenticated)
|
if (user.IsAuthenticated)
|
||||||
{
|
{
|
||||||
|
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||||
var connectionString = String.Empty;
|
var connectionString = String.Empty;
|
||||||
|
if (_showConnectionString)
|
||||||
|
{
|
||||||
|
connectionString = _connectionString;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||||
{
|
{
|
||||||
connectionString = databaseConfigControl.GetConnectionString();
|
connectionString = databaseConfigControl.GetConnectionString();
|
||||||
}
|
}
|
||||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
}
|
||||||
|
|
||||||
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.HostEmail = user.Email;
|
config.HostUsername = _hostusername;
|
||||||
config.HostPassword = _hostpassword;
|
config.HostPassword = _hostpassword;
|
||||||
|
config.HostEmail = user.Email;
|
||||||
config.HostName = user.DisplayName;
|
config.HostName = user.DisplayName;
|
||||||
config.IsNewTenant = true;
|
config.IsNewTenant = true;
|
||||||
}
|
}
|
||||||
@@ -352,8 +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;
|
||||||
|
|
||||||
ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
|
|
||||||
@@ -361,8 +410,7 @@ else
|
|||||||
if (installation.Success)
|
if (installation.Success)
|
||||||
{
|
{
|
||||||
var aliasname = config.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0];
|
var aliasname = config.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0];
|
||||||
var uri = new Uri(NavigationManager.Uri);
|
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + aliasname, true);
|
||||||
NavigationManager.NavigateTo(uri.Scheme + "://" + aliasname, true);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -381,4 +429,18 @@ else
|
|||||||
AddModuleMessage(Localizer["Message.Required.Tenant"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Required.Tenant"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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>
|
||||||
@@ -30,20 +30,16 @@ else
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<Alias> _sites;
|
private List<Alias> _sites;
|
||||||
private string _scheme;
|
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
var uri = new Uri(NavigationManager.Uri);
|
|
||||||
_scheme = uri.Scheme + "://";
|
|
||||||
|
|
||||||
var aliases = await AliasService.GetAliasesAsync();
|
var aliases = await AliasService.GetAliasesAsync();
|
||||||
_sites = new List<Alias>();
|
_sites = new List<Alias>();
|
||||||
foreach (Alias alias in aliases)
|
foreach (Alias alias in aliases)
|
||||||
{
|
{
|
||||||
if (!_sites.Exists(item => item.TenantId == alias.TenantId && item.SiteId == alias.SiteId))
|
if (alias.IsDefault && !_sites.Exists(item => item.TenantId == alias.TenantId && item.SiteId == alias.SiteId))
|
||||||
{
|
{
|
||||||
_sites.Add(alias);
|
_sites.Add(alias);
|
||||||
}
|
}
|
||||||
@@ -52,11 +48,25 @@ else
|
|||||||
|
|
||||||
private void Edit(string name)
|
private void Edit(string name)
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(_scheme + name + "/admin/site/?reload");
|
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(_scheme + name + "/?reload");
|
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
|
||||||
@@ -13,65 +14,159 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="connection" HelpText="Select a database connection (from appsettings.json)" ResourceKey="Connection">Connection: </Label>
|
||||||
<Label For="tenant" HelpText="Select the tenant for the SQL server" ResourceKey="Tenant">Tenant: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="tenant" class="form-select" value="@_connection" @onchange="(e => ConnectionChanged(e))">
|
||||||
<td>
|
<option value="-"><@Localizer["Connection.Select"]></option>
|
||||||
<select id="tenant" class="form-select" value="@_tenantid" @onchange="(e => TenantChanged(e))">
|
<option value="+"><@Localizer["Connection.Add"]></option>
|
||||||
<option value="-1"><@Localizer["Tenant.Select"]></option>
|
@foreach (var connection in _connections)
|
||||||
@foreach (Tenant tenant in _tenants)
|
|
||||||
{
|
{
|
||||||
<option value="@tenant.TenantId">@tenant.Name</option>
|
<option value="@connection.Key">@connection.Key</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
@if (_tenantid != "-1")
|
@if (_connection == "+")
|
||||||
{
|
{
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="name" HelpText="Enter the name of the connection" ResourceKey="Name">Name: </Label>
|
||||||
<Label For="database" HelpText="The database for the tenant" ResourceKey="Database">Database: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
|
||||||
<td>
|
</div>
|
||||||
<input id="database" class="form-control" @bind="@_database" readonly />
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
</tr>
|
<Label Class="col-sm-3" For="databasetype" HelpText="Select the database type" ResourceKey="DatabaseType">Type: </Label>
|
||||||
<tr>
|
<div class="col-sm-9">
|
||||||
<td>
|
@if (_databases != null)
|
||||||
<Label For="connectionstring" HelpText="The connection information for the database" ResourceKey="ConnectionString">Connection: </Label>
|
{
|
||||||
</td>
|
<div class="input-group">
|
||||||
<td>
|
<select id="databasetype" class="form-select" value="@_databasetype" @onchange="(e => DatabaseTypeChanged(e))" required>
|
||||||
<textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="2" readonly></textarea>
|
@foreach (var database in _databases)
|
||||||
</td>
|
{
|
||||||
</tr>
|
<option value="@database.Name">@Localizer[@database.Name]</option>
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="sqlQeury" HelpText="Enter the query for the SQL server" ResourceKey="SqlQuery">SQL Query: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<textarea id="sqlQeury" class="form-control" @bind="@_sql" rows="5"></textarea>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
}
|
||||||
</table>
|
</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>
|
<button type="button" class="btn btn-success" @onclick="Execute">@Localizer["Execute"]</button>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
@if (!string.IsNullOrEmpty(_results))
|
@if (_results != null)
|
||||||
{
|
{
|
||||||
@((MarkupString)_results)
|
@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 />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
private string _connection = "-";
|
||||||
|
private Dictionary<string, object> _connections;
|
||||||
private List<Tenant> _tenants;
|
private List<Tenant> _tenants;
|
||||||
private string _tenantid = "-1";
|
private List<Database> _databases;
|
||||||
private string _database = string.Empty;
|
|
||||||
|
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 _connectionstring = string.Empty;
|
||||||
|
private string _connectionstringtype = "password";
|
||||||
|
private string _connectionstringtoggle = string.Empty;
|
||||||
private string _sql = string.Empty;
|
private string _sql = string.Empty;
|
||||||
private string _results = string.Empty;
|
private List<Dictionary<string, string>> _results;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
@@ -79,7 +174,10 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_connections = await SystemService.GetSystemInfoAsync("connectionstrings");
|
||||||
_tenants = await TenantService.GetTenantsAsync();
|
_tenants = await TenantService.GetTenantsAsync();
|
||||||
|
_databases = await DatabaseService.GetDatabasesAsync();
|
||||||
|
_connectionstringtoggle = SharedLocalizer["ShowPassword"];
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -88,37 +186,129 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void TenantChanged(ChangeEventArgs e)
|
private async void ConnectionChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_tenantid = (string)e.Value;
|
_connection = (string)e.Value;
|
||||||
var tenants = await TenantService.GetTenantsAsync();
|
if (_connection != "-" && _connection != "+")
|
||||||
var _databases = await DatabaseService.GetDatabasesAsync();
|
{
|
||||||
var tenant = tenants.Find(item => item.TenantId == int.Parse(_tenantid));
|
_connectionstring = _connections[_connection].ToString();
|
||||||
|
_tenant = "";
|
||||||
|
_databasetype = "";
|
||||||
|
var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection);
|
||||||
if (tenant != null)
|
if (tenant != null)
|
||||||
{
|
{
|
||||||
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
|
_tenant = tenant.Name;
|
||||||
_connectionstring = tenant.DBConnectionString;
|
_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();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Loading Tenant {TenantId} {Error}", _tenantid, ex.Message);
|
await logger.LogError(ex, "Error Loading Connection {Connection} {Error}", _connection, ex.Message);
|
||||||
AddModuleMessage(ex.Message, MessageType.Error);
|
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()
|
private async Task Execute()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_tenantid != "-1" && !string.IsNullOrEmpty(_sql))
|
if (_databasetype != "-" && !string.IsNullOrEmpty(_sql))
|
||||||
{
|
{
|
||||||
var sqlquery = new SqlQuery { TenantId = int.Parse(_tenantid), Query = _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 = DisplayResults(sqlquery.Results);
|
_results = sqlquery.Results;
|
||||||
AddModuleMessage(Localizer["Success.QueryExecuted"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.QueryExecuted"], MessageType.Success);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -132,44 +322,4 @@ else
|
|||||||
AddModuleMessage(ex.Message, MessageType.Error);
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string DisplayResults(List<Dictionary<string, string>> results)
|
|
||||||
{
|
|
||||||
var table = string.Empty;
|
|
||||||
foreach (Dictionary<string, string> item in results)
|
|
||||||
{
|
|
||||||
if (table == string.Empty)
|
|
||||||
{
|
|
||||||
table = "<div class=\"table-responsive\">";
|
|
||||||
table += "<table class=\"table table-bordered\"><thead><tr>";
|
|
||||||
|
|
||||||
foreach (KeyValuePair<string, string> kvp in item)
|
|
||||||
{
|
|
||||||
table += "<th scope=\"col\">" + kvp.Key + "</th>";
|
|
||||||
}
|
|
||||||
|
|
||||||
table += "</tr></thead><tbody>";
|
|
||||||
}
|
|
||||||
|
|
||||||
table += "<tr>";
|
|
||||||
|
|
||||||
foreach (KeyValuePair<string, string> kvp in item)
|
|
||||||
{
|
|
||||||
table += "<td>" + kvp.Value + "</td>";
|
|
||||||
}
|
|
||||||
|
|
||||||
table += "</tr>";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (table != string.Empty)
|
|
||||||
{
|
|
||||||
table += "</tbody></table></div>";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
table = Localizer["Return.NoResult"];
|
|
||||||
}
|
|
||||||
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,105 +7,97 @@
|
|||||||
|
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Info" Heading="Info" ResourceKey="Info">
|
<TabPanel Name="Info" Heading="Info" ResourceKey="Info">
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="version" HelpText="Framework Version" ResourceKey="FrameworkVersion">Framework Version: </Label>
|
||||||
<Label For="version" HelpText="Framework Version" ResourceKey="FrameworkVersion">Framework Version: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="version" class="form-control" @bind="@_version" readonly />
|
<input id="version" class="form-control" @bind="@_version" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="clrversion" HelpText="Common Language Runtime Version" ResourceKey="CLRVersion">CLR Version: </Label>
|
||||||
<Label For="clrversion" HelpText="Common Language Runtime Version" ResourceKey="CLRVersion">CLR Version: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="clrversion" class="form-control" @bind="@_clrversion" readonly />
|
<input id="clrversion" class="form-control" @bind="@_clrversion" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="osversion" HelpText="Operating System Version" ResourceKey="OSVersion">OS Version: </Label>
|
||||||
<Label For="osversion" HelpText="Operating System Version" ResourceKey="OSVersion">OS Version: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
|
<input id="osversion" class="form-control" @bind="@_osversion" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="process" HelpText="Indicates if the current process is 32 bit or 64 bit" ResourceKey="Process">Process: </Label>
|
||||||
<Label For="serverpath" HelpText="Server Path" ResourceKey="ServerPath">Server Path: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="process" class="form-control" @bind="@_process" readonly />
|
||||||
<td>
|
</div>
|
||||||
<input id="serverpath" class="form-control" @bind="@_serverpath" readonly />
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
</tr>
|
<Label Class="col-sm-3" For="machinename" HelpText="Machine Name" ResourceKey="MachineName">Machine Name: </Label>
|
||||||
<tr>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="machinename" class="form-control" @bind="@_machinename" readonly />
|
||||||
<Label For="servertime" HelpText="Server Time" ResourceKey="ServerTime">Server Time: </Label>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="ipaddress" HelpText="Server IP Address" ResourceKey="IPAddress">IP Address: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="ipaddress" class="form-control" @bind="@_ipaddress" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="contentrootpath" class="form-control" @bind="@_contentrootpath" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="webrootpath" HelpText="Web Path" ResourceKey="WebRootPath">Web Path: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="webrootpath" class="form-control" @bind="@_webrootpath" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="servertime" HelpText="Server Date/Time (in UTC)" ResourceKey="ServerTime">Server Date/Time: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
|
<input id="servertime" class="form-control" @bind="@_servertime" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="workingset" HelpText="Memory Allocation Of Service (in MB)" ResourceKey="WorkingSet">Memory Allocation: </Label>
|
||||||
<Label For="installationid" HelpText="The Unique Identifier For Your Installation" ResourceKey="InstallationId">Installation ID: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="workingset" class="form-control" @bind="@_workingset" readonly />
|
||||||
<td>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="installationid" HelpText="The Unique Identifier For Your Installation" ResourceKey="InstallationId">Installation ID: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<input id="installationid" class="form-control" @bind="@_installationid" readonly />
|
<input id="installationid" class="form-control" @bind="@_installationid" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td> </td>
|
|
||||||
<td>
|
|
||||||
<br /><input type="checkbox" @onchange="(e => RegisterChecked(e))" /> @Localizer["Register"]
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<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" />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Options" Heading="Options" ResourceKey="Options">
|
<TabPanel Name="Options" Heading="Options" ResourceKey="Options">
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="detailederrors" HelpText="Specify If Detailed Errors Are Enabled For Blazor. This Option Should Not Not Be Enabled In Production." ResourceKey="DetailedErrors">Detailed Errors? </Label>
|
||||||
<Label For="runtime" HelpText="Blazor Runtime (Server or WebAssembly)" ResourceKey="BlazorRuntime">Blazor Runtime: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="runtime" class="form-select" @bind="@_runtime">
|
|
||||||
<option value="Server">@Localizer["Server"]</option>
|
|
||||||
<option value="WebAssembly">@Localizer["WebAssembly"]</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="rendermode" HelpText="Blazor Server Render Mode" ResourceKey="RenderMode">Render Mode: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="rendermode" class="form-select" @bind="@_rendermode">
|
|
||||||
<option value="Server">@Localizer["Server"]</option>
|
|
||||||
<option value="ServerPrerendered">@Localizer["ServerPrerendered"]</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="detailederrors" HelpText="Specify If Detailed Errors Are Enabled For Blazor. This Option Should Not Not Be Enabled In Production." ResourceKey="DetailedErrors">Detailed Errors? </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="detailederrors" class="form-select" @bind="@_detailederrors">
|
<select id="detailederrors" class="form-select" @bind="@_detailederrors">
|
||||||
<option value="true">@SharedLocalizer["True"]</option>
|
<option value="true">@SharedLocalizer["True"]</option>
|
||||||
<option value="false">@SharedLocalizer["False"]</option>
|
<option value="false">@SharedLocalizer["False"]</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="logginglevel" HelpText="The Minimum Logging Level For The Event Log. This Option Can Be Used To Control The Volume Of Items Stored In Your Event Log." ResourceKey="LoggingLevel">Logging Level: </Label>
|
||||||
<Label For="logginglevel" HelpText="The Minimum Logging Level For The Event Log. This Option Can Be Used To Control The Volume Of Items Stored In Your Event Log." ResourceKey="LoggingLevel">Logging Level: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="logginglevel" class="form-select" @bind="@_logginglevel">
|
<select id="logginglevel" class="form-select" @bind="@_logginglevel">
|
||||||
<option value="Trace">@Localizer["Trace"]</option>
|
<option value="Trace">@Localizer["Trace"]</option>
|
||||||
<option value="Debug">@Localizer["Debug"]</option>
|
<option value="Debug">@Localizer["Debug"]</option>
|
||||||
@@ -113,38 +105,73 @@
|
|||||||
<option value="Warning">@Localizer["Warning"]</option>
|
<option value="Warning">@Localizer["Warning"]</option>
|
||||||
<option value="Error">@Localizer["Error"]</option>
|
<option value="Error">@Localizer["Error"]</option>
|
||||||
<option value="Critical">@Localizer["Critical"]</option>
|
<option value="Critical">@Localizer["Critical"]</option>
|
||||||
|
<option value="None">@Localizer["None"]</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="notificationlevel" HelpText="The Minimum Logging Level For Which Notifications Should Be Sent To Host Users." ResourceKey="NotificationLevel">Notification Level: </Label>
|
||||||
<Label For="swagger" HelpText="Specify If Swagger Is Enabled For Your Server API" ResourceKey="Swagger">Swagger Enabled? </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="notificationlevel" class="form-select" @bind="@_notificationlevel">
|
||||||
<td>
|
<option value="Trace">@Localizer["Trace"]</option>
|
||||||
|
<option value="Debug">@Localizer["Debug"]</option>
|
||||||
|
<option value="Information">@Localizer["Information"]</option>
|
||||||
|
<option value="Warning">@Localizer["Warning"]</option>
|
||||||
|
<option value="Error">@Localizer["Error"]</option>
|
||||||
|
<option value="Critical">@Localizer["Critical"]</option>
|
||||||
|
<option value="None">@Localizer["None"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="swagger" HelpText="Specify If Swagger Is Enabled For Your Server API" ResourceKey="Swagger">Swagger Enabled? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
<select id="swagger" class="form-select" @bind="@_swagger">
|
<select id="swagger" class="form-select" @bind="@_swagger">
|
||||||
<option value="true">@SharedLocalizer["True"]</option>
|
<option value="true">@SharedLocalizer["True"]</option>
|
||||||
<option value="false">@SharedLocalizer["False"]</option>
|
<option value="false">@SharedLocalizer["False"]</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<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>
|
||||||
<Label For="packageservice" HelpText="Specify If The Package Service Is Enabled For Installing Modules, Themes, And Translations" ResourceKey="PackageService">Enable Package Service? </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="cachecontrol" class="form-control" @bind="@_cachecontrol" />
|
||||||
<td>
|
</div>
|
||||||
<select id="packageservice" class="form-select" @bind="@_packageservice">
|
</div>
|
||||||
<option value="true">@SharedLocalizer["True"]</option>
|
<div class="row mb-1 align-items-center">
|
||||||
<option value="false">@SharedLocalizer["False"]</option>
|
<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>
|
||||||
</select>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="packageregistryemail" HelpText="Specify The Email Address Of The User Account Used For Interacting With The Package Manager Service. This Account Is Used For Managing Packages Across Multiple Installations." ResourceKey="PackageManagerEmail">Package Manager Email: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="packageregistryemail" class="form-control" @bind="@_packageregistryemail" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<br /><br />
|
<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 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>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
@@ -152,36 +179,62 @@
|
|||||||
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 _serverpath = string.Empty;
|
private string _process = string.Empty;
|
||||||
|
private string _machinename = string.Empty;
|
||||||
|
private string _ipaddress = string.Empty;
|
||||||
|
private string _environment = string.Empty;
|
||||||
|
private string _contentrootpath = string.Empty;
|
||||||
|
private string _webrootpath = string.Empty;
|
||||||
private string _servertime = string.Empty;
|
private string _servertime = string.Empty;
|
||||||
|
private string _workingset = string.Empty;
|
||||||
private string _installationid = string.Empty;
|
private string _installationid = string.Empty;
|
||||||
|
|
||||||
private string _runtime = string.Empty;
|
|
||||||
private string _rendermode = string.Empty;
|
|
||||||
private string _detailederrors = string.Empty;
|
private string _detailederrors = string.Empty;
|
||||||
private string _logginglevel = string.Empty;
|
private string _logginglevel = 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, string> systeminfo = await SystemService.GetSystemInfoAsync();
|
var systeminfo = await SystemService.GetSystemInfoAsync("environment");
|
||||||
if (systeminfo != null)
|
if (systeminfo != null)
|
||||||
{
|
{
|
||||||
_clrversion = systeminfo["clrversion"];
|
_clrversion = systeminfo["CLRVersion"].ToString();
|
||||||
_osversion = systeminfo["osversion"];
|
_osversion = systeminfo["OSVersion"].ToString();
|
||||||
_serverpath = systeminfo["serverpath"];
|
_process = systeminfo["Process"].ToString();
|
||||||
_servertime = systeminfo["servertime"];
|
_machinename = systeminfo["MachineName"].ToString();
|
||||||
_installationid = systeminfo["installationid"];
|
_ipaddress = systeminfo["IPAddress"].ToString();
|
||||||
|
_environment = systeminfo["Environment"].ToString();
|
||||||
|
_contentrootpath = systeminfo["ContentRootPath"].ToString();
|
||||||
|
_webrootpath = systeminfo["WebRootPath"].ToString();
|
||||||
|
_servertime = systeminfo["ServerTime"].ToString() + " UTC";
|
||||||
|
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
|
||||||
|
}
|
||||||
|
|
||||||
_runtime = systeminfo["runtime"];
|
systeminfo = await SystemService.GetSystemInfoAsync("configuration");
|
||||||
_rendermode = systeminfo["rendermode"];
|
if (systeminfo != null)
|
||||||
_detailederrors = systeminfo["detailederrors"];
|
{
|
||||||
_logginglevel = systeminfo["logginglevel"];
|
_installationid = systeminfo["InstallationId"].ToString();
|
||||||
_swagger = systeminfo["swagger"];
|
_detailederrors = systeminfo["DetailedErrors"].ToString();
|
||||||
_packageservice = systeminfo["packageservice"];
|
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
|
||||||
|
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
|
||||||
|
_swagger = systeminfo["UseSwagger"].ToString();
|
||||||
|
_cachecontrol = systeminfo["CacheControl"].ToString();
|
||||||
|
_packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
|
||||||
|
_packageregistryemail = systeminfo["PackageRegistryEmail"].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
systeminfo = await SystemService.GetSystemInfoAsync("log");
|
||||||
|
if (systeminfo != null)
|
||||||
|
{
|
||||||
|
_log = systeminfo["Log"].ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,13 +242,14 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var settings = new Dictionary<string, string>();
|
var settings = new Dictionary<string, object>();
|
||||||
settings.Add("runtime", _runtime);
|
settings.Add("DetailedErrors", _detailederrors);
|
||||||
settings.Add("rendermode", _rendermode);
|
settings.Add("Logging:LogLevel:Default", _logginglevel);
|
||||||
settings.Add("detailederrors", _detailederrors);
|
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
|
||||||
settings.Add("logginglevel", _logginglevel);
|
settings.Add("UseSwagger", _swagger);
|
||||||
settings.Add("swagger", _swagger);
|
settings.Add("CacheControl", _cachecontrol);
|
||||||
settings.Add("packageservice", _packageservice);
|
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);
|
||||||
}
|
}
|
||||||
@@ -206,6 +260,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -220,20 +291,4 @@
|
|||||||
await logger.LogError(ex, "Error Restarting Application");
|
await logger.LogError(ex, "Error Restarting Application");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RegisterChecked(ChangeEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if ((bool)e.Value)
|
|
||||||
{
|
|
||||||
await InstallationService.RegisterAsync(PageState.User.Email);
|
|
||||||
AddModuleMessage(Localizer["Success.Register"], MessageType.Success);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error On Register");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -9,66 +9,177 @@
|
|||||||
|
|
||||||
<TabStrip>
|
<TabStrip>
|
||||||
<TabPanel Name="Download" ResourceKey="Download">
|
<TabPanel Name="Download" ResourceKey="Download">
|
||||||
<ModuleMessage Type="MessageType.Info" Message="Download one or more themes from the list below. Once you are ready click Install to complete the installation."></ModuleMessage>
|
<div class="row justify-content-center mb-3">
|
||||||
|
<div class="text-center">
|
||||||
<table class="table table-borderless" style=" margin: auto; width: 50% !important;">
|
<div class="form-check form-check-inline">
|
||||||
<tr>
|
<input id="free" class="form-check-input" type="radio" checked="@(_price == "free")" name="Price" @onchange="@(() => PriceChanged("free"))" />
|
||||||
<td>
|
<label class="form-check-label" for="free">@SharedLocalizer["Free"]</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input id="paid" class="form-check-input" type="radio" checked="@(_price == "paid")" name="Price" @onchange="@(() => PriceChanged("paid"))" />
|
||||||
|
<label class="form-check-label" for="paid">@SharedLocalizer["Paid"]</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-center mb-3">
|
||||||
|
<div class="col">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">@Localizer["Product"]</span>
|
||||||
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
|
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
|
||||||
</td>
|
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
|
||||||
<td>
|
|
||||||
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
|
<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>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
|
</div>
|
||||||
@if (_packages != null)
|
<div class="row mb-3">
|
||||||
|
<div class="col">
|
||||||
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
if (_packages.Count > 0)
|
<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")
|
||||||
{
|
{
|
||||||
<Pager Items="@_packages">
|
<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>
|
<Row>
|
||||||
<td>
|
<div class="m-2 p-2 d-flex justify-content-center">
|
||||||
<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 />
|
<div class="container-fluid px-0">
|
||||||
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
|
<div class="row g-0 mb-2">
|
||||||
<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> | @SharedLocalizer["Search.Source"]: <strong>@context.PackageUrl</strong>
|
<div class="col-4">
|
||||||
</td>
|
<a href="@context.ProductUrl" target="_blank">
|
||||||
<td style="vertical-align: middle;">
|
@if (context.LogoUrl != null)
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadTheme(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
|
{
|
||||||
</td>
|
<img src="@context.LogoUrl" class="img-fluid" alt="@context.Name" />
|
||||||
</Row>
|
|
||||||
</Pager>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<br />
|
<img src="/package.png" class="img-fluid" alt="@context.Name" />
|
||||||
<div class="mx-auto text-center">
|
}
|
||||||
@Localizer["Search.NoResults"]
|
</a>
|
||||||
</div>
|
</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">
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" HelpText="Upload one or more theme packages." ResourceKey="Theme">Theme: </Label>
|
||||||
<Label HelpText="Upload one or more theme packages. Once they are uploaded click Install to complete the installation." ResourceKey="Theme">Theme: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<FileManager Folder="@Constants.PackagesFolder" UploadMultiple="true" OnUpload="OnUpload" />
|
||||||
<td>
|
</div>
|
||||||
<FileManager Filter="nupkg" ShowFiles="false" Folder="Packages" UploadMultiple="@true" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
|
|
||||||
<button type="button" class="btn btn-success" @onclick="InstallThemes">@SharedLocalizer["Install"]</button>
|
@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>
|
||||||
|
}
|
||||||
|
|
||||||
<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 _sort = "popularity";
|
||||||
private string _search = "";
|
private string _search = "";
|
||||||
|
private string _productname = "";
|
||||||
|
private string _license = "";
|
||||||
|
private string _packageid = "";
|
||||||
|
private string _version = "";
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
@@ -77,6 +188,7 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LoadThemes();
|
await LoadThemes();
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -87,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);
|
_packages = await PackageService.GetPackagesAsync("theme", _search, _price, "", _sort);
|
||||||
|
|
||||||
if (_packages != null)
|
if (_packages != null)
|
||||||
{
|
{
|
||||||
@@ -100,59 +214,102 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HideProgressIndicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void PriceChanged(string price)
|
||||||
|
{
|
||||||
|
_price = price;
|
||||||
|
_sort = "popularity";
|
||||||
|
await LoadThemes();
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
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 async Task InstallThemes()
|
private void OnPageChange(int page)
|
||||||
|
{
|
||||||
|
_page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void SortChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_sort = (string)e.Value;
|
||||||
|
await LoadThemes();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HideModal()
|
||||||
|
{
|
||||||
|
_productname = "";
|
||||||
|
_license = "";
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetPackage(string packageid, string version)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ThemeService.InstallThemesAsync();
|
var package = await PackageService.GetPackageAsync(packageid, version, false);
|
||||||
AddModuleMessage(string.Format(Localizer["Success.Theme.Install"], NavigateUrl("admin/system")), MessageType.Success);
|
if (package != null)
|
||||||
|
{
|
||||||
|
_productname = package.Name;
|
||||||
|
if (!string.IsNullOrEmpty(package.License))
|
||||||
|
{
|
||||||
|
_license = package.License.Replace("\n", "<br />");
|
||||||
|
}
|
||||||
|
_packageid = package.PackageId;
|
||||||
|
_version = package.Version;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await logger.LogError("Error Getting Package {PackageId} {Version}", packageid, version);
|
||||||
|
AddModuleMessage(Localizer["Error.Theme.Download"], MessageType.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Installing Theme");
|
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
|
||||||
|
AddModuleMessage(Localizer["Error.Theme.Download"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DownloadTheme(string packageid, string version)
|
private async Task DownloadPackage()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PackageService.DownloadPackageAsync(packageid, version, "Packages");
|
await PackageService.DownloadPackageAsync(_packageid, _version);
|
||||||
await logger.LogInformation("Theme {ThemeName} {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 = "";
|
||||||
|
_license = "";
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Downloading Module {ThemeName} {Version}", packageid, version);
|
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packageid, _version);
|
||||||
AddModuleMessage(Localizer["Error.Theme.Download"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.Theme.Download"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnUpload()
|
||||||
|
{
|
||||||
|
AddModuleMessage(string.Format(Localizer["Success.Theme.Download"], NavigateUrl("admin/system")), MessageType.Success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,28 +11,22 @@
|
|||||||
|
|
||||||
@if (_templates != null)
|
@if (_templates != null)
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="owner" HelpText="Enter the name of the organization who is developing this theme. It should not contain spaces or punctuation." ResourceKey="OwnerName">Owner Name: </Label>
|
||||||
<Label For="owner" HelpText="Enter the name of the organization who is developing this theme. It should not contain spaces or punctuation." ResourceKey="OwnerName">Owner Name: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="owner" class="form-control" @bind="@_owner" />
|
<input id="owner" class="form-control" @bind="@_owner" />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="module" HelpText="Enter a name for this theme. It should not contain spaces or punctuation." ResourceKey="ThemeName">Theme Name: </Label>
|
||||||
<Label For="module" HelpText="Enter a name for this theme. It should not contain spaces or punctuation." ResourceKey="ThemeName">Theme Name: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="module" class="form-control" @bind="@_theme" />
|
<input id="module" class="form-control" @bind="@_theme" />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="template" HelpText="Select a theme template. Templates are located in the wwwroot/Themes/Templates folder on the server." ResourceKey="Template">Template: </Label>
|
||||||
<Label For="template" HelpText="Select a theme template. Templates are located in the wwwroot/Themes/Templates folder on the server." ResourceKey="Template">Template: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="template" class="form-select" @onchange="(e => TemplateChanged(e))">
|
<select id="template" class="form-select" @onchange="(e => TemplateChanged(e))">
|
||||||
<option value="-"><@Localizer["Template.Select"]></option>
|
<option value="-"><@Localizer["Template.Select"]></option>
|
||||||
@foreach (Template template in _templates)
|
@foreach (Template template in _templates)
|
||||||
@@ -40,13 +34,11 @@
|
|||||||
<option value="@template.Name">@template.Title</option>
|
<option value="@template.Name">@template.Title</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
||||||
<Label For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="reference" class="form-select" @bind="@_reference">
|
<select id="reference" class="form-select" @bind="@_reference">
|
||||||
@foreach (string version in _versions)
|
@foreach (string version in _versions)
|
||||||
{
|
{
|
||||||
@@ -57,20 +49,19 @@
|
|||||||
}
|
}
|
||||||
<option value="local">@SharedLocalizer["LocalVersion"]</option>
|
<option value="local">@SharedLocalizer["LocalVersion"]</option>
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
@if (!string.IsNullOrEmpty(_location))
|
@if (!string.IsNullOrEmpty(_location))
|
||||||
{
|
{
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="location" HelpText="Location where the theme will be created" ResourceKey="Location">Location: </Label>
|
||||||
<Label For="location" HelpText="Location where the theme will be created" ResourceKey="Location">Location: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="module" class="form-control" @bind="@_location" readonly />
|
<input id="module" class="form-control" @bind="@_location" readonly />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
}
|
}
|
||||||
</table>
|
</div>
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Theme.Create"]</button>
|
<button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Theme.Create"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
}
|
}
|
||||||
@@ -81,19 +72,26 @@
|
|||||||
private List<Template> _templates;
|
private List<Template> _templates;
|
||||||
private string _template = "-";
|
private string _template = "-";
|
||||||
private string[] _versions;
|
private string[] _versions;
|
||||||
private string _reference = Constants.Version;
|
private string _reference = "local";
|
||||||
private string _minversion = "2.0.0";
|
private string _minversion = "2.0.0";
|
||||||
private string _location = string.Empty;
|
private string _location = string.Empty;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
if (!NavigationManager.BaseUri.Contains("localhost:"))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_templates = await ThemeService.GetThemeTemplatesAsync();
|
_templates = await ThemeService.GetThemeTemplatesAsync();
|
||||||
_versions = Constants.ReleaseVersions.Split(',').Where(item => Version.Parse(item).CompareTo(Version.Parse("2.0.0")) >= 0).ToArray();
|
_versions = Constants.ReleaseVersions.Split(',').Where(item => Version.Parse(item).CompareTo(Version.Parse("2.0.0")) >= 0).ToArray();
|
||||||
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -107,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);
|
||||||
@@ -126,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)
|
||||||
@@ -147,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,35 +14,58 @@
|
|||||||
}
|
}
|
||||||
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 scope="col">@SharedLocalizer["Name"]</th>
|
<th style="width: 1px;"> </th>
|
||||||
<th scope="col">@SharedLocalizer["Version"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
|
<th>@SharedLocalizer["Version"]</th>
|
||||||
|
<th>@Localizer["Enabled"]</th>
|
||||||
|
<th>@SharedLocalizer["Support"]</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>
|
<td>
|
||||||
@if (UpgradeAvailable(context.PackageName, context.Version))
|
@if (context.IsEnabled)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadTheme(context.PackageName, context.Version))>@SharedLocalizer["Upgrade"]</button>
|
<span>@SharedLocalizer["Yes"]</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>@SharedLocalizer["No"]</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@((MarkupString)SupportLink(context.PackageName, context.Version))
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@((MarkupString)PurchaseLink(context.PackageName))
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@{
|
||||||
|
var version = UpgradeAvailable(context.PackageName, context.Version);
|
||||||
|
}
|
||||||
|
@if (version != context.Version)
|
||||||
|
{
|
||||||
|
<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>
|
||||||
}
|
}
|
||||||
@@ -57,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)
|
||||||
{
|
{
|
||||||
@@ -69,27 +93,63 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool UpgradeAvailable(string packagename, string version)
|
private string PurchaseLink(string packagename)
|
||||||
{
|
{
|
||||||
var upgradeavailable = false;
|
string link = "";
|
||||||
if (_packages != null)
|
if (!string.IsNullOrEmpty(packagename) && _packages != null)
|
||||||
{
|
{
|
||||||
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
|
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
|
||||||
if (package != null)
|
if (package != null)
|
||||||
{
|
{
|
||||||
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0);
|
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 upgradeavailable;
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DownloadTheme(string packagename, string version)
|
private async Task DownloadTheme(string packagename, string version)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PackageService.DownloadPackageAsync(packagename, version, "Packages");
|
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)
|
||||||
@@ -113,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,103 +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
|
|
||||||
|
|
||||||
<table class="table table-borderless">
|
|
||||||
<tr>
|
|
||||||
<td width="30%">
|
|
||||||
<Label For="name" HelpText="The name of the theme" ResourceKey="Name">Name: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="name" class="form-control" @bind="@_name" disabled />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="themename" class="form-control" @bind="@_themeName" disabled />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="version" class="form-control" @bind="@_version" disabled />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="owner" class="form-control" @bind="@_owner" disabled />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="url" class="form-control" @bind="@_url" disabled />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="contact" class="form-control" @bind="@_contact" disabled />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<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,38 +7,68 @@
|
|||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
<TabStrip>
|
@if (_initialized)
|
||||||
|
{
|
||||||
|
<TabStrip>
|
||||||
<TabPanel Name="Download" ResourceKey="Download">
|
<TabPanel Name="Download" ResourceKey="Download">
|
||||||
@if (_package != null && _upgradeavailable)
|
@if (_package != null && _upgradeavailable)
|
||||||
{
|
{
|
||||||
<ModuleMessage Type="MessageType.Info" Message="Select The Download Button To Download The Framework Upgrade Package And Then Select Upgrade"></ModuleMessage>
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" HelpText="Specify if you want to backup files during the upgrade process. Disabling this option will reduce the time required for the upgrade." ResourceKey="Backup">Backup Files? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="backup" class="form-select" @bind="@_backup">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
@if (!_downloaded)
|
||||||
|
{
|
||||||
<button type="button" class="btn btn-primary" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@SharedLocalizer["Download"] @_package.Version</button>
|
<button type="button" class="btn btn-primary" @onclick=@(async () => await Download(Constants.PackageId, @_package.Version))>@SharedLocalizer["Download"] @_package.Version</button>
|
||||||
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<ModuleMessage Type="MessageType.Info" Message="Framework Is Already Up To Date"></ModuleMessage>
|
<button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<ModuleMessage Type="MessageType.Info" Message=@Localizer["Message.Text"]></ModuleMessage>
|
||||||
}
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Upload" ResourceKey="Upload">
|
<TabPanel Name="Upload" ResourceKey="Upload">
|
||||||
<ModuleMessage Type="MessageType.Info" Message="Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade"></ModuleMessage>
|
<div class="container">
|
||||||
<table class="table table-borderless">
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
|
<FileManager Folder="@Constants.PackagesFolder" />
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<FileManager Filter="nupkg" ShowFiles="false" Folder="Packages" />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<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>
|
||||||
</tr>
|
<div class="col-sm-9">
|
||||||
</table>
|
<select id="backup" class="form-select" @bind="@_backup">
|
||||||
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
<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;
|
||||||
|
|
||||||
@@ -46,7 +76,15 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
List<Package> packages = await PackageService.GetPackagesAsync("framework");
|
if (NavigationManager.BaseUri.Contains("localhost:"))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Localhost.Text"], MessageType.Info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Disclaimer.Text"], MessageType.Warning);
|
||||||
|
|
||||||
|
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
|
||||||
if (packages != null)
|
if (packages != null)
|
||||||
{
|
{
|
||||||
_package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault();
|
_package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault();
|
||||||
@@ -59,6 +97,8 @@
|
|||||||
_package = new Package { Name = Constants.PackageId, Version = Constants.Version };
|
_package = new Package { Name = Constants.PackageId, Version = Constants.Version };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -73,8 +113,8 @@
|
|||||||
AddModuleMessage(Localizer["Info.Upgrade.Wait"], MessageType.Info);
|
AddModuleMessage(Localizer["Info.Upgrade.Wait"], MessageType.Info);
|
||||||
ShowProgressIndicator();
|
ShowProgressIndicator();
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
await interop.RedirectBrowser(NavigateUrl(), 20);
|
await interop.RedirectBrowser(NavigateUrl(), 10);
|
||||||
await InstallationService.Upgrade();
|
await InstallationService.Upgrade(bool.Parse(_backup));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -87,13 +127,17 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await PackageService.DownloadPackageAsync(packageid, version, "Packages");
|
ShowProgressIndicator();
|
||||||
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version, "Packages");
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
108
Oqtane.Client/Modules/Admin/UrlMappings/Add.razor
Normal file
108
Oqtane.Client/Modules/Admin/UrlMappings/Add.razor
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.UrlMappings
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IUrlMappingService UrlMappingService
|
||||||
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="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="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 class="row mb-1 align-items-center">
|
||||||
|
<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">
|
||||||
|
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveUrlMapping">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
|
||||||
|
private string _url = string.Empty;
|
||||||
|
private string _mappedurl = string.Empty;
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
|
private async Task SaveUrlMapping()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
|
{
|
||||||
|
if (_url != _mappedurl)
|
||||||
|
{
|
||||||
|
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
|
||||||
|
url = url + (!string.IsNullOrEmpty(PageState.Alias.Path) ? PageState.Alias.Path + "/" : "");
|
||||||
|
|
||||||
|
_url = (_url.StartsWith("/")) ? _url.Substring(1) : _url;
|
||||||
|
_url = (!_url.StartsWith("http")) ? url + _url : _url;
|
||||||
|
|
||||||
|
_mappedurl = _mappedurl.Replace(url, "");
|
||||||
|
_mappedurl = (_mappedurl.StartsWith("/") && _mappedurl != "/") ? _mappedurl.Substring(1) : _mappedurl;
|
||||||
|
|
||||||
|
if (_url.StartsWith(url))
|
||||||
|
{
|
||||||
|
var urlmapping = new UrlMapping();
|
||||||
|
urlmapping.SiteId = PageState.Site.SiteId;
|
||||||
|
urlmapping.Url = new Route(_url, PageState.Alias.Path).PagePath;
|
||||||
|
urlmapping.MappedUrl = _mappedurl;
|
||||||
|
urlmapping.Requests = 0;
|
||||||
|
urlmapping.CreatedOn = DateTime.UtcNow;
|
||||||
|
urlmapping.RequestedOn = DateTime.UtcNow;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
urlmapping = await UrlMappingService.AddUrlMappingAsync(urlmapping);
|
||||||
|
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
95
Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor
Normal file
95
Oqtane.Client/Modules/Admin/UrlMappings/Edit.razor
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.UrlMappings
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IUrlMappingService UrlMappingService
|
||||||
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="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">
|
||||||
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<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">
|
||||||
|
<input id="mappedurl" class="form-control" @bind="@_mappedurl" maxlength="500" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br /><br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveUrlMapping">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
|
||||||
|
private int _urlmappingid;
|
||||||
|
private string _url = string.Empty;
|
||||||
|
private string _mappedurl = string.Empty;
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_urlmappingid = Int32.Parse(PageState.QueryString["id"]);
|
||||||
|
var urlmapping = await UrlMappingService.GetUrlMappingAsync(_urlmappingid);
|
||||||
|
if (urlmapping != null)
|
||||||
|
{
|
||||||
|
_url = urlmapping.Url;
|
||||||
|
_mappedurl = urlmapping.MappedUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading UrlMapping {UrlMappingId} {Error}", _urlmappingid, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.LoadUrlMapping"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveUrlMapping()
|
||||||
|
{
|
||||||
|
validated = true;
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
if (await interop.FormValid(form))
|
||||||
|
{
|
||||||
|
if (_url != _mappedurl)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var url = PageState.Uri.Scheme + "://" + PageState.Uri.Authority + "/";
|
||||||
|
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);
|
||||||
|
urlmapping.MappedUrl = _mappedurl;
|
||||||
|
urlmapping = await UrlMappingService.UpdateUrlMappingAsync(urlmapping);
|
||||||
|
await logger.LogInformation("UrlMapping Saved {UrlMapping}", urlmapping);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving UrlMapping {UrlMappingId} {Error}", _urlmappingid, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SaveUrlMapping"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.DuplicateUrlMapping"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
151
Oqtane.Client/Modules/Admin/UrlMappings/Index.razor
Normal file
151
Oqtane.Client/Modules/Admin/UrlMappings/Index.razor
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.UrlMappings
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IUrlMappingService UrlMappingService
|
||||||
|
@inject ISiteService SiteService
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
@if (_urlMappings == null)
|
||||||
|
{
|
||||||
|
<p><em>@SharedLocalizer["Loading"]</em></p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<TabStrip>
|
||||||
|
<TabPanel Name="Urls" Heading="Urls" ResourceKey="Urls">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<ActionLink Action="Add" Text="Add Url Mapping" ResourceKey="AddUrlMapping" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<select id="type" class="form-select custom-select" @onchange="(e => MappedChanged(e))">
|
||||||
|
<option value="true">@Localizer["Mapped"]</option>
|
||||||
|
<option value="false">@Localizer["Broken"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<Pager Items="@_urlMappings" SearchProperties="Url">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["Url"]</th>
|
||||||
|
<th>@Localizer["Requests"]</th>
|
||||||
|
<th>@Localizer["Requested"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
||||||
|
<td><ActionDialog Header="Delete Url Mapping" Message="@string.Format(Localizer["Confirm.DeleteUrlMapping"], context.Url)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUrlMapping(context))" ResourceKey="DeleteUrlMapping" /></td>
|
||||||
|
<td>
|
||||||
|
<a href="@Utilities.TenantUrl(PageState.Alias, context.Url)">@context.Url</a>
|
||||||
|
@if (_mapped)
|
||||||
|
{
|
||||||
|
@((MarkupString)"<br />>> ")<a href="@((context.MappedUrl.StartsWith("http") ? context.MappedUrl : Utilities.TenantUrl(PageState.Alias, context.MappedUrl)))">@context.MappedUrl</a>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>@context.Requests</td>
|
||||||
|
<td>@UtcToLocal(context.RequestedOn)</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="capturebrokenurls" HelpText="Specify if broken Urls should be captured automatically and saved in Url Mappings" ResourceKey="CaptureBrokenUrls">Capture Broken Urls? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="capturebrokenurls" class="form-select" @bind="@_capturebrokenurls" >
|
||||||
|
<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="retention" HelpText="Number of days of broken urls to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private bool _mapped = true;
|
||||||
|
private List<UrlMapping> _urlMappings;
|
||||||
|
private string _capturebrokenurls;
|
||||||
|
private int _retention = 30;
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
await GetUrlMappings();
|
||||||
|
_capturebrokenurls = PageState.Site.CaptureBrokenUrls.ToString();
|
||||||
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_retention = int.Parse(SettingService.GetSetting(settings, "UrlMappingRetention", "30"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void MappedChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_mapped = bool.Parse(e.Value.ToString());
|
||||||
|
await GetUrlMappings();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error On TypeChanged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteUrlMapping(UrlMapping urlMapping)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await UrlMappingService.DeleteUrlMappingAsync(urlMapping.UrlMappingId);
|
||||||
|
await logger.LogInformation("UrlMapping Deleted {UrlMapping}", urlMapping);
|
||||||
|
await GetUrlMappings();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Deleting UrlMapping {UrlMapping} {Error}", urlMapping, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.DeleteUrlMapping"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetUrlMappings()
|
||||||
|
{
|
||||||
|
_urlMappings = await UrlMappingService.GetUrlMappingsAsync(PageState.Site.SiteId, _mapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveSiteSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var site = PageState.Site;
|
||||||
|
site.CaptureBrokenUrls = bool.Parse(_capturebrokenurls);
|
||||||
|
await SiteService.UpdateSiteAsync(site);
|
||||||
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "UrlMappingRetention", _retention.ToString(), true);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||||
|
|
||||||
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,44 +2,40 @@
|
|||||||
@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
|
||||||
|
|
||||||
@if (PageState.User != null)
|
@if (PageState.User != null)
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="to" HelpText="Enter the user you wish to send a message to" ResourceKey="To">To: </Label>
|
||||||
<Label For="to" HelpText="Enter the username you wish to send a message to" ResourceKey="To">To: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="username" />
|
||||||
<td>
|
</div>
|
||||||
<input id="to" class="form-control" @bind="@username" />
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
</tr>
|
<Label Class="col-sm-3" For="subject" HelpText="Enter the subject of the message" ResourceKey="Subject">Subject: </Label>
|
||||||
<tr>
|
<div class="col-sm-9">
|
||||||
<td>
|
|
||||||
<Label For="subject" HelpText="Enter the subject of the message" ResourceKey="Subject">Subject: </Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="subject" class="form-control" @bind="@subject" />
|
<input id="subject" class="form-control" @bind="@subject" />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="message" HelpText="Enter the message" ResourceKey="Message">Message: </Label>
|
||||||
<Label For="message" HelpText="Enter the message" ResourceKey="Message">Message: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<textarea id="message" class="form-control" @bind="@body" rows="5" />
|
<textarea id="message" class="form-control" @bind="@body" rows="5" />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
|
<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 = "";
|
||||||
|
|
||||||
@@ -47,23 +43,37 @@
|
|||||||
|
|
||||||
public override string Title => "Send Notification";
|
public override string Title => "Send Notification";
|
||||||
|
|
||||||
|
private async Task<Dictionary<string, string>> GetUsers(string filter)
|
||||||
|
{
|
||||||
|
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||||
|
return users.Where(item => item.User.Username.Contains(filter, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToDictionary(item => item.UserId.ToString(), item => item.User.Username);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task Send()
|
private async Task Send()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var user = await UserService.GetUserAsync(username, PageState.Site.SiteId);
|
if (!string.IsNullOrEmpty(username.Key) && !string.IsNullOrEmpty(subject))
|
||||||
|
{
|
||||||
|
var user = await UserService.GetUserAsync(int.Parse(username.Key), ModuleState.SiteId);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body, null);
|
var notification = new Notification(PageState.Site.SiteId, PageState.User, user, subject, body);
|
||||||
notification = await NotificationService.AddNotificationAsync(notification);
|
notification = await NotificationService.AddNotificationAsync(notification);
|
||||||
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
|
await logger.LogInformation("Notification Created {NotificationId}", notification.NotificationId);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Required"], MessageType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message);
|
await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message);
|
||||||
|
|||||||
@@ -1,4 +1,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
|
||||||
@@ -6,101 +8,122 @@
|
|||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject INotificationService NotificationService
|
@inject INotificationService NotificationService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
|
@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="@photo.Url" alt="@displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
|
@if (PageState.User != null && _photo != null)
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<br />
|
|
||||||
}
|
|
||||||
<TabStrip>
|
|
||||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
|
||||||
@if (PageState.User != null)
|
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<img src="@ImageUrl(_photofileid, 400, 400)" alt="@_displayname" style="max-width: 400px" class="rounded-circle mx-auto d-block">
|
||||||
<tr>
|
}
|
||||||
<td width="30%">
|
else
|
||||||
<Label For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
|
{
|
||||||
</td>
|
<br />
|
||||||
<td>
|
}
|
||||||
<input id="username" class="form-control" @bind="@username" readonly />
|
<TabStrip>
|
||||||
</td>
|
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||||
</tr>
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
<tr>
|
<div class="container">
|
||||||
<td>
|
<div class="row mb-1 align-items-center">
|
||||||
<Label 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="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||||
<input id ="password" type="password" class="form-control" @bind="@password" autocomplete="new-password" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<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>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
|
<div class="input-group">
|
||||||
</td>
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||||
<td>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
<input id="confirm" type="password" class="form-control" @bind="@confirm" autocomplete="new-password" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<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 For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<div class="input-group">
|
||||||
<td>
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||||
<input id="email" class="form-control" @bind="@email" />
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
</div>
|
||||||
<td>
|
@if (_allowtwofactor)
|
||||||
<Label For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
{
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<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>
|
||||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
<div class="col-sm-9">
|
||||||
</td>
|
<select id="twofactor" class="form-select" @bind="@_twofactor" required>
|
||||||
</tr>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
<tr>
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
<td>
|
</select>
|
||||||
<Label For="@photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
}
|
||||||
<FileManager FileId="@photofileid" @ref="filemanager" />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
||||||
</tr>
|
<div class="col-sm-9">
|
||||||
</table>
|
<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="Your full name" 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="@_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>
|
||||||
|
<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="row mb-1 align-items-center">
|
||||||
<table class="table table-borderless">
|
@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)
|
||||||
{
|
{
|
||||||
<tr>
|
<div class="col text-center pb-2">
|
||||||
<th colspan="2" style="text-align: center;">
|
|
||||||
@p.Category
|
@p.Category
|
||||||
</th>
|
</div>
|
||||||
</tr>
|
_category = p.Category;
|
||||||
category = p.Category;
|
|
||||||
}
|
}
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||||
<Label For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@if (!string.IsNullOrEmpty(p.Options))
|
@if (!string.IsNullOrEmpty(p.Options))
|
||||||
{
|
{
|
||||||
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
@if (!string.IsNullOrEmpty(p.Autocomplete))
|
||||||
|
{
|
||||||
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))" autocomplete="@p.Autocomplete">
|
||||||
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||||
@@ -116,32 +139,105 @@ else
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@if (p.IsRequired)
|
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
|
||||||
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
|
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||||
|
{
|
||||||
|
<option value="@option" selected>@option</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
<option value="@option">@option</option>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</td>
|
</select>
|
||||||
</tr>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</table>
|
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>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||||
}
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Notifications" ResourceKey="Notifications">
|
<TabPanel Name="Notifications" ResourceKey="Notifications">
|
||||||
@if (notifications != null)
|
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" />
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<select class="form-select" @onchange="(e => FilterChanged(e))">
|
||||||
|
<option value="to">@Localizer["Inbox"]</option>
|
||||||
|
<option value="from">@Localizer["Items.Sent"]</option>
|
||||||
|
</select>
|
||||||
|
<br />
|
||||||
|
@if (_filter == "to")
|
||||||
{
|
{
|
||||||
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="SendNotification" />
|
@if (_notifications.Any())
|
||||||
<br /><br />
|
|
||||||
@if (filter == "to")
|
|
||||||
{
|
{
|
||||||
<Pager Items="@notifications">
|
<Pager Items="@_notifications">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@@ -150,11 +246,21 @@ else
|
|||||||
<th>@Localizer["Received"]</th>
|
<th>@Localizer["Received"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></td>
|
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||||
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||||
<td>@context.FromDisplayName</td>
|
|
||||||
|
@if (context.IsRead)
|
||||||
|
{
|
||||||
|
<td>@(string.IsNullOrEmpty(context.FromDisplayName) ? SharedLocalizer["System"] : context.FromDisplayName)</td>
|
||||||
<td>@context.Subject</td>
|
<td>@context.Subject</td>
|
||||||
<td>@context.CreatedOn</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>
|
</Row>
|
||||||
<Detail>
|
<Detail>
|
||||||
<td colspan="2"></td>
|
<td colspan="2"></td>
|
||||||
@@ -166,28 +272,59 @@ else
|
|||||||
context.Body = context.Body.Split(input)[0];
|
context.Body = context.Body.Split(input)[0];
|
||||||
context.Body = context.Body.Replace("\n", "");
|
context.Body = context.Body.Replace("\n", "");
|
||||||
context.Body = context.Body.Replace("\r", "");
|
context.Body = context.Body.Replace("\r", "");
|
||||||
} }
|
}
|
||||||
@(context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body)
|
_notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
|
||||||
</td>
|
}
|
||||||
</Detail>
|
@if (context.IsRead)
|
||||||
</Pager>
|
{
|
||||||
|
@_notificationSummary
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<Pager Items="@notifications">
|
<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>
|
<Header>
|
||||||
<th> </th>
|
<th style="width: 1px;"></th>
|
||||||
<th> </th>
|
<th style="width: 1px;"></th>
|
||||||
<th>@Localizer["To"]</th>
|
<th>@Localizer["To"]</th>
|
||||||
<th>@Localizer["Subject"]</th>
|
<th>@Localizer["Subject"]</th>
|
||||||
<th>@Localizer["Sent"]</th>
|
<th>@Localizer["Sent"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" /></td>
|
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" ResourceKey="ViewNotification" ReturnUrl="@NavigateUrl(PageState.Page.Path, "tab=Notifications")" /></td>
|
||||||
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
<td><ActionDialog Header="Delete Notification" Message="Are You Sure You Wish To Delete This Notification?" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" ResourceKey="DeleteNotification" /></td>
|
||||||
|
|
||||||
|
@if (context.IsRead)
|
||||||
|
{
|
||||||
<td>@context.ToDisplayName</td>
|
<td>@context.ToDisplayName</td>
|
||||||
<td>@context.Subject</td>
|
<td>@context.Subject</td>
|
||||||
<td>@context.CreatedOn</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>
|
</Row>
|
||||||
<Detail>
|
<Detail>
|
||||||
<td colspan="2"></td>
|
<td colspan="2"></td>
|
||||||
@@ -199,63 +336,115 @@ else
|
|||||||
context.Body = context.Body.Split(input)[0];
|
context.Body = context.Body.Split(input)[0];
|
||||||
context.Body = context.Body.Replace("\n", "");
|
context.Body = context.Body.Replace("\n", "");
|
||||||
context.Body = context.Body.Replace("\r", "");
|
context.Body = context.Body.Replace("\r", "");
|
||||||
} }
|
|
||||||
@(context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body)
|
|
||||||
</td>
|
|
||||||
</Detail>
|
|
||||||
</Pager>
|
|
||||||
}
|
}
|
||||||
<br /><hr />
|
_notificationSummary = context.Body.Length > 100 ? (context.Body.Substring(0, 97) + "...") : context.Body;
|
||||||
<select class="form-select" @onchange="(e => FilterChanged(e))">
|
|
||||||
<option value="to">@Localizer["Inbox"]</option>
|
|
||||||
<option value="from">@Localizer["Items.Sent"]</option>
|
|
||||||
</select>
|
|
||||||
}
|
}
|
||||||
</TabPanel>
|
@if (context.IsRead)
|
||||||
</TabStrip>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private string username = string.Empty;
|
|
||||||
private string password = string.Empty;
|
|
||||||
private string confirm = string.Empty;
|
|
||||||
private string email = string.Empty;
|
|
||||||
private string displayname = string.Empty;
|
|
||||||
private FileManager filemanager;
|
|
||||||
private int photofileid = -1;
|
|
||||||
private File photo = null;
|
|
||||||
private List<Profile> profiles;
|
|
||||||
private Dictionary<string, string> settings;
|
|
||||||
private string category = string.Empty;
|
|
||||||
private string filter = "to";
|
|
||||||
private List<Notification> notifications;
|
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
|
||||||
{
|
{
|
||||||
try
|
@_notificationSummary
|
||||||
{
|
|
||||||
if (PageState.User != null)
|
|
||||||
{
|
|
||||||
username = PageState.User.Username;
|
|
||||||
email = PageState.User.Email;
|
|
||||||
displayname = PageState.User.DisplayName;
|
|
||||||
|
|
||||||
if (PageState.User.PhotoFileId != null)
|
|
||||||
{
|
|
||||||
photofileid = PageState.User.PhotoFileId.Value;
|
|
||||||
photo = await FileService.GetFileAsync(photofileid);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
photofileid = -1;
|
<b>@_notificationSummary</b>
|
||||||
photo = null;
|
}
|
||||||
|
</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 {
|
||||||
|
private List<Models.TimeZone> _timezones;
|
||||||
|
private bool _initialized = false;
|
||||||
|
private string _passwordrequirements;
|
||||||
|
private string _username = string.Empty;
|
||||||
|
private string _password = string.Empty;
|
||||||
|
private string _passwordtype = "password";
|
||||||
|
private string _togglepassword = string.Empty;
|
||||||
|
private string _confirm = string.Empty;
|
||||||
|
private bool _allowtwofactor = false;
|
||||||
|
private string _twofactor = "False";
|
||||||
|
private string _email = string.Empty;
|
||||||
|
private string _displayname = string.Empty;
|
||||||
|
private FileManager _filemanager;
|
||||||
|
private int _folderid = -1;
|
||||||
|
private string _timezoneid = string.Empty;
|
||||||
|
private int _photofileid = -1;
|
||||||
|
private File _photo = null;
|
||||||
|
private string _imagefiles = string.Empty;
|
||||||
|
|
||||||
|
private List<Profile> _profiles;
|
||||||
|
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;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
_allowtwofactor = (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "true");
|
||||||
|
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||||
|
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||||
|
|
||||||
|
if (PageState.User != null)
|
||||||
|
{
|
||||||
|
_username = PageState.User.Username;
|
||||||
|
_twofactor = PageState.User.TwoFactorRequired.ToString();
|
||||||
|
_email = PageState.User.Email;
|
||||||
|
_displayname = PageState.User.DisplayName;
|
||||||
|
_timezoneid = PageState.User.TimeZoneId;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(_email))
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.User.NoEmail"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
// get user folder
|
||||||
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
|
||||||
|
if (folder != null)
|
||||||
|
{
|
||||||
|
_folderid = folder.FolderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PageState.User.PhotoFileId != null)
|
||||||
|
{
|
||||||
|
_photofileid = PageState.User.PhotoFileId.Value;
|
||||||
|
_photo = await FileService.GetFileAsync(_photofileid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_photofileid = -1;
|
||||||
|
_photo = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_userSettings = PageState.User.Settings;
|
||||||
|
var sitesettings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_imagefiles = SettingService.GetSetting(_userSettings, "ImageFiles", Constants.ImageFiles);
|
||||||
|
_imagefiles = (string.IsNullOrEmpty(_imagefiles)) ? Constants.ImageFiles : _imagefiles;
|
||||||
|
|
||||||
await LoadNotificationsAsync();
|
await LoadNotificationsAsync();
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -271,37 +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)
|
||||||
|
{
|
||||||
|
if (ValidateProfiles())
|
||||||
{
|
{
|
||||||
var user = PageState.User;
|
var user = PageState.User;
|
||||||
user.Username = username;
|
user.Username = _username;
|
||||||
user.Password = password;
|
user.Password = _password;
|
||||||
user.Email = email;
|
user.TwoFactorRequired = bool.Parse(_twofactor);
|
||||||
user.DisplayName = (displayname == string.Empty ? username : displayname);
|
user.Email = _email;
|
||||||
user.PhotoFileId = filemanager.GetFileId();
|
user.DisplayName = (_displayname == string.Empty ? _username : _displayname);
|
||||||
|
user.TimeZoneId = _timezoneid;
|
||||||
|
user.PhotoFileId = _filemanager.GetFileId();
|
||||||
if (user.PhotoFileId == -1)
|
if (user.PhotoFileId == -1)
|
||||||
{
|
{
|
||||||
user.PhotoFileId = null;
|
user.PhotoFileId = null;
|
||||||
}
|
}
|
||||||
|
if (user.PhotoFileId != null)
|
||||||
|
{
|
||||||
|
_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 SettingService.UpdateUserSettingsAsync(_userSettings, PageState.User.UserId);
|
||||||
await logger.LogInformation("User Profile Saved");
|
await logger.LogInformation("User Profile Saved");
|
||||||
|
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||||
|
{
|
||||||
|
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
|
||||||
{
|
{
|
||||||
@@ -312,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)
|
||||||
{
|
{
|
||||||
@@ -320,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)
|
||||||
@@ -378,10 +643,54 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task DeleteAllNotifications()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ShowProgressIndicator();
|
||||||
|
foreach(var Notification in _notifications)
|
||||||
|
{
|
||||||
|
if (!Notification.IsDeleted)
|
||||||
|
{
|
||||||
|
Notification.IsDeleted = true;
|
||||||
|
await NotificationService.UpdateNotificationAsync(Notification);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await NotificationService.DeleteNotificationAsync(Notification.NotificationId);
|
||||||
|
}
|
||||||
|
await logger.LogInformation("Notification Deleted {Notification}", Notification);
|
||||||
|
}
|
||||||
|
await logger.LogInformation("Notifications Permanently Deleted");
|
||||||
|
await LoadNotificationsAsync();
|
||||||
|
HideProgressIndicator();
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Deleting Notifications {Error}", ex.Message);
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
HideProgressIndicator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TogglePassword()
|
||||||
|
{
|
||||||
|
if (_passwordtype == "password")
|
||||||
|
{
|
||||||
|
_passwordtype = "text";
|
||||||
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordtype = "password";
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,98 +8,71 @@
|
|||||||
|
|
||||||
@if (PageState.User != null)
|
@if (PageState.User != null)
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
|
||||||
<tr>
|
|
||||||
<td width="30%">
|
|
||||||
<label class="control-label">@Localizer["Title"] </label>
|
|
||||||
</td>
|
|
||||||
@if (title == "From")
|
@if (title == "From")
|
||||||
{
|
{
|
||||||
<td>
|
<div class="container">
|
||||||
<input class="form-control" @bind="@username" readonly />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="username" HelpText="The user who sent the message" ResourceKey="From">From:</Label>
|
||||||
}
|
<div class="col-sm-9">
|
||||||
@if (title == "To")
|
<input id="username" class="form-control" @bind="@username" readonly />
|
||||||
{
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<input class="form-control" @bind="@username" />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="subject" HelpText="The subject of the message" ResourceKey="Subject">Subject:</Label>
|
||||||
}
|
<div class="col-sm-9">
|
||||||
</tr>
|
<input id="subject" class="form-control" @bind="@subject" readonly />
|
||||||
<tr>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<label class="control-label">@Localizer["Subject"] </label>
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label class="col-sm-3" For="date" HelpText="The date the message was sent" ResourceKey="Date">Sent:</Label>
|
||||||
@if (title == "From")
|
<div class="col-sm-9">
|
||||||
{
|
<input id="date" class="form-control" @bind="@createdon" readonly />
|
||||||
<td>
|
</div>
|
||||||
<input class="form-control" @bind="@subject" readonly />
|
</div>
|
||||||
</td>
|
<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>
|
||||||
@if (title == "To")
|
<div class="col-sm-9">
|
||||||
{
|
<textarea id="message" class="form-control" @bind="@body" rows="5" readonly />
|
||||||
<td>
|
</div>
|
||||||
<input class="form-control" @bind="@subject" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
@if (title == "From")
|
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<label class="control-label">@Localizer["Date"] </label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input class="form-control" @bind="@createdon" readonly />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
@if (title == "From")
|
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<label class="control-label">@Localizer["Message"] </label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<textarea class="form-control" @bind="@body" rows="5" readonly />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
@if (title == "To")
|
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<label class="control-label">@Localizer["Message"] </label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<textarea class="form-control" @bind="@body" rows="5" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
@if (reply != string.Empty)
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (title == "From")
|
<div class="container">
|
||||||
{
|
<div class="row mb-1 align-items-center">
|
||||||
<button type="button" class="btn btn-primary" @onclick="Reply">@Localizer["Reply"]</button>
|
<Label Class="col-sm-3" For="username" HelpText="The user who will be the recipient of the message" ResourceKey="To">To:</Label>
|
||||||
}
|
<div class="col-sm-9">
|
||||||
}
|
<input id="username" class="form-control" @bind="@username" readonly />
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
</div>
|
||||||
<br />
|
</div>
|
||||||
<br />
|
<div class="row mb-1 align-items-center">
|
||||||
@if (title == "To")
|
<Label Class="col-sm-3" For="subject" HelpText="The subject of the message" ResourceKey="Subject">Subject:</Label>
|
||||||
{
|
<div class="col-sm-9">
|
||||||
<div class="control-group">
|
<input id="subject" class="form-control" @bind="@subject" readonly="@(!reply)" />
|
||||||
<label class="control-label">@Localizer["OriginalMessage"] </label>
|
</div>
|
||||||
<textarea 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 {
|
||||||
@@ -109,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";
|
||||||
@@ -149,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)
|
||||||
@@ -166,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,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)
|
||||||
|
{
|
||||||
|
<TabStrip>
|
||||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||||
@if (profiles != null)
|
@if (_profiles != null)
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="username" HelpText="A unique username for a user. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
|
||||||
<Label 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">
|
||||||
</td>
|
<input id="username" class="form-control" @bind="@_username" />
|
||||||
<td>
|
</div>
|
||||||
<input id="username" class="form-control" @bind="@username" />
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
</tr>
|
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
|
||||||
<tr>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input id="email" class="form-control" @bind="@_email" />
|
||||||
<Label For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
<div class="row mb-1 align-items-center">
|
||||||
<input id="password" type="password" class="form-control" @bind="@password" />
|
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
||||||
</td>
|
<div class="col-sm-9">
|
||||||
</tr>
|
<input id="displayname" class="form-control" @bind="@_displayname" />
|
||||||
<tr>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<Label For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<input id="confirm" type="password" class="form-control" @bind="@confirm" />
|
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||||
</td>
|
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||||
</tr>
|
@foreach (var timezone in _timezones)
|
||||||
<tr>
|
{
|
||||||
<td>
|
<option value="@timezone.Id">@timezone.DisplayName</option>
|
||||||
<Label For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
|
}
|
||||||
</td>
|
</select>
|
||||||
<td>
|
</div>
|
||||||
<input id="email" class="form-control" @bind="@email" />
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
</tr>
|
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
|
||||||
<tr>
|
<div class="col-sm-9">
|
||||||
<td>
|
<select id="notify" class="form-select" @bind="@_notify" required>
|
||||||
<Label For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||||
</td>
|
<option value="False">@SharedLocalizer["No"]</option>
|
||||||
<td>
|
</select>
|
||||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
|
||||||
}
|
}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||||
@if (profiles != null)
|
<div class="container">
|
||||||
{
|
<div class="row mb-1 align-items-center">
|
||||||
<table class="table table-borderless">
|
@foreach (Profile profile in _profiles)
|
||||||
@foreach (Profile profile in profiles)
|
|
||||||
{
|
{
|
||||||
var p = profile;
|
var p = profile;
|
||||||
if (p.Category != category)
|
if (p.Category != _category)
|
||||||
{
|
{
|
||||||
<tr>
|
<div class="col text-center pb-2">
|
||||||
<th colspan="2" style="text-align: center;">
|
<strong>@p.Category</strong>
|
||||||
@p.Category
|
</div>
|
||||||
</th>
|
_category = p.Category;
|
||||||
</tr>
|
|
||||||
category = p.Category;
|
|
||||||
}
|
}
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||||
<Label For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
@if (!string.IsNullOrEmpty(p.Options))
|
||||||
<td>
|
|
||||||
@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))">
|
||||||
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||||
|
{
|
||||||
|
<option value="@option" selected>@option</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
<option value="@option">@option</option>
|
||||||
}
|
}
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
}
|
||||||
</table>
|
</select>
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (p.Rows == 1)
|
||||||
|
{
|
||||||
|
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
||||||
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
}
|
||||||
|
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
|
||||||
<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 confirm = string.Empty;
|
private string _username = string.Empty;
|
||||||
private string email = string.Empty;
|
private string _email = string.Empty;
|
||||||
private string displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private List<Profile> profiles;
|
private string _timezoneid = string.Empty;
|
||||||
private Dictionary<string, string> settings;
|
private string _notify = "True";
|
||||||
private string category = string.Empty;
|
private List<Profile> _profiles;
|
||||||
|
private Dictionary<string, string> _settings;
|
||||||
|
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
|
||||||
{
|
{
|
||||||
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||||
settings = new Dictionary<string, string>();
|
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||||
|
_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 = new User();
|
|
||||||
user.SiteId = PageState.Site.SiteId;
|
user.SiteId = PageState.Site.SiteId;
|
||||||
user.Username = username;
|
user.Username = _username;
|
||||||
user.Password = password;
|
user.Password = ""; // will be auto generated
|
||||||
user.Email = email;
|
user.Email = _email;
|
||||||
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
|
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||||
|
user.TimeZoneId = _timezoneid;
|
||||||
user.PhotoFileId = null;
|
user.PhotoFileId = null;
|
||||||
|
user.SuppressNotification = !bool.Parse(_notify);
|
||||||
|
|
||||||
user = await UserService.AddUserAsync(user);
|
user = await UserService.AddUserAsync(user);
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
|
await SettingService.UpdateUserSettingsAsync(_settings, user.UserId);
|
||||||
await logger.LogInformation("User Created {User}", user);
|
await logger.LogInformation("User Created {User}", user);
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await logger.LogError("Error Adding User {Username} {Email}", username, email);
|
await logger.LogError("Error Adding User {Username} {Email}", _username, _email);
|
||||||
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Username.Exists"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -174,32 +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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,230 +1,293 @@
|
|||||||
@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>
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<br />
|
|
||||||
}
|
|
||||||
<TabStrip>
|
|
||||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||||
@if (profiles != null)
|
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||||
{
|
<div class="container">
|
||||||
<table class="table table-borderless">
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<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>
|
||||||
<td width="30%">
|
<div class="col-sm-9">
|
||||||
<Label For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username"></Label>
|
<input id="username" class="form-control" @bind="@_username" readonly />
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<input id="username" class="form-control" @bind="@username" readonly />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password">Password:</Label>
|
||||||
</tr>
|
<div class="col-sm-9">
|
||||||
<tr>
|
<div class="input-group">
|
||||||
<td>
|
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" />
|
||||||
<Label For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<input id="password" type="password" class="form-control" @bind="@password" />
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
</tr>
|
<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>
|
||||||
<tr>
|
<div class="col-sm-9">
|
||||||
<td>
|
<div class="input-group">
|
||||||
<Label For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
|
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" />
|
||||||
</td>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
<td>
|
</div>
|
||||||
<input id="confirm" type="password" class="form-control" @bind="@confirm" />
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<tr>
|
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email">Email:</Label>
|
||||||
<td>
|
<div class="col-sm-9">
|
||||||
<Label For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
|
<input id="email" class="form-control" @bind="@_email" />
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</div>
|
||||||
<input id="email" class="form-control" @bind="@email" />
|
<div class="row mb-1 align-items-center">
|
||||||
</td>
|
<Label Class="col-sm-3" For="confirmed" HelpText="Indicates if the user's email is verified" ResourceKey="Confirmed">Confirmed?</Label>
|
||||||
</tr>
|
<div class="col-sm-9">
|
||||||
<tr>
|
<select id="confirmed" class="form-select" @bind="@_confirmed">
|
||||||
<td>
|
|
||||||
<Label For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="@photofileid.ToString()" HelpText="A photo of the user" ResourceKey="Photo"></Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<FileManager FileId="@photofileid" @ref="filemanager" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<Label For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<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>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName">Full Name:</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>
|
||||||
|
@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>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||||
@if (profiles != null)
|
<div class="container">
|
||||||
{
|
<div class="row mb-1 align-items-center">
|
||||||
<table class="table table-borderless">
|
@foreach (Profile profile in _profiles)
|
||||||
@foreach (Profile profile in profiles)
|
|
||||||
{
|
{
|
||||||
var p = profile;
|
var p = profile;
|
||||||
if (p.Category != category)
|
if (p.Category != _category)
|
||||||
{
|
{
|
||||||
<tr>
|
<div class="col text-center pb-2">
|
||||||
<th colspan="2" style="text-align: center;">
|
<strong>@p.Category</strong>
|
||||||
@p.Category
|
</div>
|
||||||
</th>
|
_category = p.Category;
|
||||||
</tr>
|
|
||||||
category = p.Category;
|
|
||||||
}
|
}
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
||||||
<Label For="@p.Name" HelpText="@p.Description">@p.Title</Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
@if (!string.IsNullOrEmpty(p.Options))
|
||||||
<td>
|
|
||||||
@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))">
|
||||||
|
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
|
||||||
|
{
|
||||||
|
<option value="@option" selected>@option</option>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
|
<option value="@option">@option</option>
|
||||||
}
|
}
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
}
|
||||||
</table>
|
</select>
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (p.Rows == 1)
|
||||||
|
{
|
||||||
|
<input id="@p.Name" class="form-control" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<textarea id="@p.Name" class="form-control" rows="@p.Rows" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" @attributes="@(p.MaxLength > 0 ? new Dictionary<string, object> {{"maxlength", p.MaxLength }} : null)"></textarea>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabStrip>
|
</TabStrip>
|
||||||
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
<br />
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !_ishost)
|
||||||
<br />
|
{
|
||||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
|
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
|
||||||
|
}
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _isdeleted == "True")
|
||||||
|
{
|
||||||
|
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
||||||
|
}
|
||||||
|
<br /><br />
|
||||||
|
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private 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 confirm = string.Empty;
|
private int _userid;
|
||||||
private string email = string.Empty;
|
private string _username = string.Empty;
|
||||||
private string displayname = string.Empty;
|
private string _password = string.Empty;
|
||||||
private FileManager filemanager;
|
private string _passwordtype = "password";
|
||||||
private int photofileid = -1;
|
private string _togglepassword = string.Empty;
|
||||||
private File photo = null;
|
private string _confirm = string.Empty;
|
||||||
private List<Profile> profiles;
|
private string _email = string.Empty;
|
||||||
private Dictionary<string, string> settings;
|
private string _confirmed = string.Empty;
|
||||||
private string category = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private string createdby;
|
private string _timezoneid = string.Empty;
|
||||||
private DateTime createdon;
|
private string _isdeleted;
|
||||||
private string modifiedby;
|
private string _lastlogin;
|
||||||
private DateTime modifiedon;
|
private string _lastipaddress;
|
||||||
private string deletedby;
|
private bool _ishost = false;
|
||||||
private DateTime? deletedon;
|
|
||||||
private string isdeleted;
|
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
private List<Profile> _profiles;
|
||||||
|
private Dictionary<string, string> _settings;
|
||||||
|
private string _category = string.Empty;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
private string _createdby;
|
||||||
|
private DateTime _createdon;
|
||||||
|
private string _modifiedby;
|
||||||
|
private DateTime _modifiedon;
|
||||||
|
private string _deletedby;
|
||||||
|
private DateTime? _deletedon;
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// OnParametersSetAsync is called when the edit modal is closed - in which case there is no id parameter
|
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||||
if (PageState.QueryString.ContainsKey("id"))
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
_profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
||||||
|
_timezones = await TimeZoneService.GetTimeZonesAsync();
|
||||||
|
|
||||||
|
if (PageState.QueryString.ContainsKey("id") && int.TryParse(PageState.QueryString["id"], out int UserId))
|
||||||
{
|
{
|
||||||
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
|
_userid = UserId;
|
||||||
userid = Int32.Parse(PageState.QueryString["id"]);
|
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
|
||||||
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
username = user.Username;
|
_username = user.Username;
|
||||||
email = user.Email;
|
_email = user.Email;
|
||||||
displayname = user.DisplayName;
|
_confirmed = user.EmailConfirmed.ToString();
|
||||||
if (user.PhotoFileId != null)
|
_displayname = user.DisplayName;
|
||||||
{
|
_timezoneid = PageState.User.TimeZoneId;
|
||||||
photofileid = user.PhotoFileId.Value;
|
_isdeleted = user.IsDeleted.ToString();
|
||||||
photo = await FileService.GetFileAsync(photofileid);
|
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
|
||||||
}
|
_lastipaddress = user.LastIPAddress;
|
||||||
else
|
_ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
||||||
{
|
|
||||||
photofileid = -1;
|
_settings = user.Settings;
|
||||||
photo = null;
|
_createdby = user.CreatedBy;
|
||||||
}
|
_createdon = user.CreatedOn;
|
||||||
settings = await SettingService.GetUserSettingsAsync(user.UserId);
|
_modifiedby = user.ModifiedBy;
|
||||||
createdby = user.CreatedBy;
|
_modifiedon = user.ModifiedOn;
|
||||||
createdon = user.CreatedOn;
|
_deletedby = user.DeletedBy;
|
||||||
modifiedby = user.ModifiedBy;
|
_deletedon = user.DeletedOn;
|
||||||
modifiedon = user.ModifiedOn;
|
|
||||||
deletedby = user.DeletedBy;
|
|
||||||
deletedon = user.DeletedOn;
|
|
||||||
isdeleted = user.IsDeleted.ToString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_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())
|
||||||
|
{
|
||||||
|
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
|
||||||
user.SiteId = PageState.Site.SiteId;
|
user.SiteId = PageState.Site.SiteId;
|
||||||
user.Username = username;
|
user.Username = _username;
|
||||||
user.Password = password;
|
user.Password = _password;
|
||||||
user.Email = email;
|
user.Email = _email;
|
||||||
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
|
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||||
user.PhotoFileId = null;
|
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||||
user.PhotoFileId = filemanager.GetFileId();
|
user.TimeZoneId = _timezoneid;
|
||||||
if (user.PhotoFileId == -1)
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
user.PhotoFileId = null;
|
user.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
||||||
}
|
}
|
||||||
|
|
||||||
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
|
|
||||||
|
|
||||||
user = await UserService.UpdateUserAsync(user);
|
user = await UserService.UpdateUserAsync(user);
|
||||||
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
|
if (user != null)
|
||||||
|
{
|
||||||
|
await SettingService.UpdateUserSettingsAsync(_settings, user.UserId);
|
||||||
await logger.LogInformation("User Saved {User}", user);
|
await logger.LogInformation("User Saved {User}", user);
|
||||||
|
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
|
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
@@ -236,32 +299,97 @@ 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()
|
||||||
|
{
|
||||||
|
if (_passwordtype == "password")
|
||||||
|
{
|
||||||
|
_passwordtype = "text";
|
||||||
|
_togglepassword = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_passwordtype = "password";
|
||||||
|
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
@inject IUserRoleService UserRoleService
|
@inject IUserRoleService UserRoleService
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
|
@inject ISiteService SiteService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (userroles == null)
|
@if (users == null)
|
||||||
{
|
{
|
||||||
<p>
|
<p>
|
||||||
<em>@SharedLocalizer["Loading"]</em>
|
<em>@SharedLocalizer["Loading"]</em>
|
||||||
@@ -14,111 +15,846 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<TabStrip>
|
||||||
<tr>
|
<TabPanel Name="Users" Heading="Users" ResourceKey="Users">
|
||||||
<td>
|
<ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
|
||||||
<div><ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" /></div>
|
<ActionLink Text="Import Users" Class="btn btn-secondary ms-2" Action="Users" Security="SecurityAccessLevel.Admin" ResourceKey="ImportUsers"/>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input class="form-control" @bind="@_search" />
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button class="btn btn-secondary" @onclick="OnSearch">@SharedLocalizer["Search"]</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<Pager Items="@userroles">
|
<Pager Items="@users" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("Username"))">@Localizer["Username"]<i class="@(SetSortIcon("Username"))"></i></th>
|
||||||
|
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("DisplayName"))">@Localizer["Name"]<i class="@(SetSortIcon("DisplayName"))"></i></th>
|
||||||
|
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("Email"))">@Localizer["Email"]<i class="@(SetSortIcon("Email"))"></i></th>
|
||||||
|
<th class="app-sort-th link-primary text-decoration-underline" @onclick="@(() => SortTable("LastLoginOn"))">@Localizer["LastLoginOn"]<i class="@(SetSortIcon("LastLoginOn"))"></i></th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td>
|
<td>
|
||||||
<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="EditUser" />
|
<ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditUser" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.Role.Name == RoleNames.Host)" ResourceKey="DeleteUser" />
|
<ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUser(context))" Disabled="@(context.UserId == PageState.User.UserId || context.User.IsDeleted)" ResourceKey="DeleteUser" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" />
|
<ActionLink Action="Roles" Text="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
|
||||||
</td>
|
</td>
|
||||||
|
<td>@context.User.Username</td>
|
||||||
<td>@context.User.DisplayName</td>
|
<td>@context.User.DisplayName</td>
|
||||||
|
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.Email))</td>
|
||||||
|
<td>@((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", UtcToLocal(context.User.LastLoginOn)) : "")</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
|
||||||
|
<div class="container">
|
||||||
|
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="allowregistration" HelpText="Do you want anonymous visitors to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="allowregistration" class="form-select" @bind="@_allowregistration">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (_allowregistration == "true")
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="registerurl" HelpText="Optionally provide a custom registration url" ResourceKey="RegisterUrl">Register Url:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="registerurl" class="form-control" @bind="@_registerurl" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="profileurl" HelpText="Optionally provide a custom profile url" ResourceKey="ProfileUrl">Profile Url:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Two Factor?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
||||||
|
<option value="false">@Localizer["Disabled"]</option>
|
||||||
|
<option value="true">@Localizer["Optional"]</option>
|
||||||
|
<option value="required">@Localizer["Required"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="cookiename" HelpText="You can choose to use a custom authentication cookie name for each site. However please be aware that if you want to share an authentication cookie between sites on the same domain they need to use a consistent cookie name. Also be aware that changing the authentication cookie name will logout all current users." ResourceKey="CookieName">Cookie Name:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="cookieexpiration" class="form-control" @bind="@_cookieexpiration" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="alwaysremember" HelpText="Enabling this option will set a permanent cookie in conjunction with the Cookie Expiration Timespan, which will automatically sign in users the next time they visit the site. By default the site will use session cookies." ResourceKey="AlwaysRemember">Always Remember User?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="alwaysremember" class="form-select" @bind="@_alwaysremember">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="logouteverywhere" HelpText="Do you want users to be logged out of every active session on any device, or only their current session?" ResourceKey="LogoutEverywhere">Logout Everywhere?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="logouteverywhere" class="form-select" @bind="@_logouteverywhere">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</Section>
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="minimumlength" class="form-control" @bind="@_minimumlength" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="uniquecharacters" HelpText="The Minimum Number Of Unique Characters Which A Password Must Contain" ResourceKey="UniqueCharacters">Unique Characters:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="uniquecharacters" class="form-control" @bind="@_uniquecharacters" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="requiredigit" HelpText="Indicate If Passwords Must Contain A Digit" ResourceKey="RequireDigit">Require Digit?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="requiredigit" class="form-select" @bind="@_requiredigit" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="requireupper" HelpText="Indicate If Passwords Must Contain An Upper Case Character" ResourceKey="RequireUpper">Require Uppercase?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="requireupper" class="form-select" @bind="@_requireupper" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="requirelower" HelpText="Indicate If Passwords Must Contain A Lower Case Character" ResourceKey="RequireLower">Require Lowercase?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="requirelower" class="form-select" @bind="@_requirelower" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="requirepunctuation" HelpText="Indicate if Passwords Must Contain A Non-alphanumeric Character (ie. Punctuation)" ResourceKey="RequirePunctuation">Require Punctuation?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="requirepunctuation" class="form-select" @bind="@_requirepunctuation" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
<Section Name="Lockout" Heading="Lockout Settings" ResourceKey="LockoutSettings">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="maximum" HelpText="The Maximum Number Of Sign In Attempts Before A User Is Locked Out" ResourceKey="MaximumFailures">Maximum Failures:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="maximum" class="form-control" @bind="@_maximumfailures" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="lockoutduration" HelpText="The Number Of Minutes A User Should Be Locked Out" ResourceKey="LockoutDuration">Lockout Duration:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="lockoutduration" class="form-control" @bind="@_lockoutduration" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="provider" HelpText="Select the external login provider" ResourceKey="Provider">Provider:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<select id="provider" class="form-select" value="@_provider" @onchange="(e => ProviderChanged(e))">
|
||||||
|
@foreach (var provider in Shared.ExternalLoginProviders.Providers)
|
||||||
|
{
|
||||||
|
<option value="@provider.Name">@Localizer[provider.Name]</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
@if (!string.IsNullOrEmpty(_providerurl))
|
||||||
|
{
|
||||||
|
<a href="@_providerurl" class="btn btn-secondary" target="_new">@Localizer["Info"]</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="providertype" HelpText="Select the external login provider type" ResourceKey="ProviderType">Provider Type:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
|
||||||
|
<option value="" selected><@Localizer["Not Specified"]></option>
|
||||||
|
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OIDC"]</option>
|
||||||
|
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth2"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (_providertype != "")
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="providername" HelpText="Specify a friendly name for the external login provider which will be displayed on the Login page" ResourceKey="ProviderName">Provider Name:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="providername" class="form-control" @bind="@_providername" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="authority" HelpText="The Authority Url or Issuer Url associated with the OpenID Connect provider" ResourceKey="Authority">Authority:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="authority" class="form-control" @bind="@_authority" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="metadataurl" HelpText="The discovery endpoint for obtaining metadata for this provider. Only specify if the OpenID Connect provider does not use the standard approach (ie. /.well-known/openid-configuration)" ResourceKey="MetadataUrl">Metadata Url:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="metadataurl" class="form-control" @bind="@_metadataurl" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (_providertype == AuthenticationProviderTypes.OAuth2)
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="authorizationurl" HelpText="The endpoint for obtaining an Authorization Code" ResourceKey="AuthorizationUrl">Authorization Url:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="authorizationurl" class="form-control" @bind="@_authorizationurl" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="tokenurl" HelpText="The endpoint for obtaining an Auth Token" ResourceKey="TokenUrl">Token Url:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="tokenurl" class="form-control" @bind="@_tokenurl" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="userinfourl" HelpText="The endpoint for obtaining user information. This should be an API or Page Url which contains the users email address." ResourceKey="UserInfoUrl">User Info Url:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="userinfourl" class="form-control" @bind="@_userinfourl" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (_providertype != "")
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="clientid" HelpText="The Client ID from the provider" ResourceKey="ClientID">Client ID:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="clientid" class="form-control" @bind="@_clientid" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="clientsecret" HelpText="The Client Secret from the provider" ResourceKey="ClientSecret">Client Secret:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="@_clientsecrettype" id="clientsecret" class="form-control" @bind="@_clientsecret" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@ToggleClientSecret">@_toggleclientsecret</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="authresponsetype" HelpText="Specify the authorization response type. The default is Authorization Code which is considered to be the most secure option based on the latest OAuth specification." ResourceKey="AuthResponseType">Authorization Response Type:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="authresponsetype" class="form-select" @bind="@_authresponsetype" required>
|
||||||
|
<option value="code">@Localizer["AuthFlow.Code"]</option>
|
||||||
|
<option value="code id_token">@Localizer["AuthFlow.CodeIdToken"]</option>
|
||||||
|
<option value="code id_token token">@Localizer["AuthFlow.CodeIdTokenToken"]</option>
|
||||||
|
<option value="code token">@Localizer["AuthFlow.CodeToken"]</option>
|
||||||
|
<option value="id_token">@Localizer["AuthFlow.IdToken"]</option>
|
||||||
|
<option value="id_token token">@Localizer["AuthFlow.IdTokenToken"]</option>
|
||||||
|
<option value="token">@Localizer["AuthFlow.Token"]</option>
|
||||||
|
<option value="none">@Localizer["AuthFlow.None"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="scopes" class="form-control" @bind="@_scopes" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="parameters" HelpText="Optionally specify any additional parameters as name/value pairs to send to the provider (separated by commas if there are multiple)." ResourceKey="Parameters">Parameters:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="parameters" class="form-control" @bind="@_parameters" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="pkce" HelpText="Indicate if the provider supports Proof Key for Code Exchange (PKCE)" ResourceKey="PKCE">Use PKCE?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="pkce" class="form-select" @bind="@_pkce" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="redirecturl" HelpText="The Redirect Url (or Callback Url) which usually needs to be registered with the provider" ResourceKey="RedirectUrl">Redirect Url:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="reviewclaims" HelpText="This option will record the full list of Claims returned by the Provider in the Event Log. It should only be used for testing purposes. External Login will be restricted when this option is enabled." ResourceKey="ReviewClaims">Review Claims?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<select id="reviewclaims" class="form-select" @bind="@_reviewclaims" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
@if (_reviewclaims == "true")
|
||||||
|
{
|
||||||
|
<a href="@_externalloginurl" target="_blank" class="btn btn-secondary">@SharedLocalizer["Test"]</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="identifierclaimtype" HelpText="Specify the type name of the unique user identifier claim provided by the provider. The default value is 'sub'." ResourceKey="IdentifierClaimType">Identifier Claim:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="identifierclaimtype" class="form-control" @bind="@_identifierclaimtype" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="nameclaimtype" HelpText="Optionally specify the type name of the user's name claim provided by the provider. The typical value is 'name'." ResourceKey="NameClaimType">Name Claim:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="nameclaimtype" class="form-control" @bind="@_nameclaimtype" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="emailclaimtype" HelpText="Optionally specify the type name of the email address claim provided by the provider. The typical value is 'email'," ResourceKey="EmailClaimType">Email Claim:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the roles claim provided by the provider" ResourceKey="RoleClaimType">Roles Claim:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="roleclaimmappings" HelpText="Optionally provide a comma delimited list of role names provided by the identity provider, as well as mappings to your site roles." ResourceKey="RoleClaimMappings">Role Claim Mappings:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="roleclaimmappings" class="form-control" @bind="@_roleclaimmappings" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="synchronizeroles" HelpText="This option will add or remove role assignments so that the site roles exactly match the roles provided by the identity provider" ResourceKey="SynchronizeRoles">Synchronize Roles?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<select id="synchronizeroles" class="form-select" @bind="@_synchronizeroles" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="profileclaimtypes" HelpText="A comma delimited list of user profile claims provided by the provider, as well as mappings to your user profile definition. For example if the provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'." ResourceKey="ProfileClaimTypes">User Profile Claims:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="savetokens" HelpText="Specify whether access and refresh tokens should be saved after a successful login. The default is false to reduce the size of the authentication cookie." ResourceKey="SaveTokens">Save Tokens?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="savetokens" class="form-select" @bind="@_savetokens" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="domainfilter" class="form-control" @bind="@_domainfilter" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="createusers" HelpText="Do you want new users to be created automatically? If you disable this option, users must already be registered on the site in order to sign in with their external login." ResourceKey="CreateUsers">Create New Users?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="createusers" class="form-select" @bind="@_createusers">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="verifyusers" HelpText="Do you want existing users to perform an additional email verification step to link their external login? If you disable this option, existing users will be linked automatically." ResourceKey="VerifyUsers">Verify Existing Users?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="verifyusers" class="form-select" @bind="@_verifyusers">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="allowhostrole" HelpText="Indicate if host roles are supported from the identity provider. Please use caution with this option as it allows the host user to administrate every site within your installation." ResourceKey="AllowHostRole">Allow Host Role?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="allowhostrole" class="form-select" @bind="@_allowhostrole" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Local Login?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</Section>
|
||||||
|
<Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="jwtsecret" HelpText="If you want to want to provide API access, please specify a secret which will be used to encrypt your tokens. The secret should be 16 characters or more to ensure optimal security. Please note that if you change this secret, all existing tokens will become invalid and will need to be regenerated." ResourceKey="Secret">Secret:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="@_secrettype" id="jwtsecret" class="form-control" @bind="@_secret" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@ToggleSecret">@_togglesecret</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="issuer" HelpText="Optionally provide the issuer of the token" ResourceKey="Issuer">Issuer:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="issuer" class="form-control" @bind="@_issuer" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="audience" HelpText="Optionally provide the audience for the token" ResourceKey="Audience">Audience:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="audience" class="form-control" @bind="@_audience" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="lifetime" HelpText="The number of minutes for which a token should be valid" ResourceKey="Lifetime">Lifetime:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="lifetime" class="form-control" @bind="@_lifetime" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="token" HelpText="Select the Create Token button to generate a long-lived access token (valid for 1 year). Be sure to store this token in a safe location as you will not be able to access it in the future." ResourceKey="Token">Access Token:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="token" class="form-control" @bind="@_token" />
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="@CreateToken">@Localizer["CreateToken"]</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private List<UserRole> allroles;
|
private List<UserRole> users;
|
||||||
private List<UserRole> userroles;
|
|
||||||
private string _search;
|
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
private string _allowregistration;
|
||||||
|
private string _registerurl;
|
||||||
|
private string _profileurl;
|
||||||
|
private string _twofactor;
|
||||||
|
private string _cookiename;
|
||||||
|
private string _cookieexpiration;
|
||||||
|
private string _alwaysremember;
|
||||||
|
private string _logouteverywhere;
|
||||||
|
|
||||||
|
private string _minimumlength;
|
||||||
|
private string _uniquecharacters;
|
||||||
|
private string _requiredigit;
|
||||||
|
private string _requireupper;
|
||||||
|
private string _requirelower;
|
||||||
|
private string _requirepunctuation;
|
||||||
|
private string _maximumfailures;
|
||||||
|
private string _lockoutduration;
|
||||||
|
|
||||||
|
private string _provider;
|
||||||
|
private string _providerurl;
|
||||||
|
private string _providertype;
|
||||||
|
private string _providername;
|
||||||
|
private string _authority;
|
||||||
|
private string _metadataurl;
|
||||||
|
private string _authorizationurl;
|
||||||
|
private string _tokenurl;
|
||||||
|
private string _userinfourl;
|
||||||
|
private string _clientid;
|
||||||
|
private string _clientsecret;
|
||||||
|
private string _clientsecrettype = "password";
|
||||||
|
private string _toggleclientsecret = string.Empty;
|
||||||
|
private string _authresponsetype;
|
||||||
|
private string _scopes;
|
||||||
|
private string _parameters;
|
||||||
|
private string _pkce;
|
||||||
|
private string _redirecturl;
|
||||||
|
private string _reviewclaims;
|
||||||
|
private string _externalloginurl;
|
||||||
|
private string _identifierclaimtype;
|
||||||
|
private string _nameclaimtype;
|
||||||
|
private string _emailclaimtype;
|
||||||
|
private string _roleclaimtype;
|
||||||
|
private string _roleclaimmappings;
|
||||||
|
private string _synchronizeroles;
|
||||||
|
private string _profileclaimtypes;
|
||||||
|
private string _savetokens;
|
||||||
|
private string _domainfilter;
|
||||||
|
private string _createusers;
|
||||||
|
private string _verifyusers;
|
||||||
|
private string _allowhostrole;
|
||||||
|
private string _allowsitelogin;
|
||||||
|
|
||||||
|
private string _secret;
|
||||||
|
private string _secrettype = "password";
|
||||||
|
private string _togglesecret = string.Empty;
|
||||||
|
private string _issuer;
|
||||||
|
private string _audience;
|
||||||
|
private string _lifetime;
|
||||||
|
private string _token;
|
||||||
|
|
||||||
|
private bool isSortedAscending;
|
||||||
|
private string activeSortColumn;
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
|
await LoadUsersAsync(true);
|
||||||
await LoadSettingsAsync();
|
|
||||||
userroles = Search(_search);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
||||||
|
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||||
|
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||||
|
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
||||||
|
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
||||||
|
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
|
||||||
|
_alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false");
|
||||||
|
_logouteverywhere = SettingService.GetSetting(settings, "LoginOptions:LogoutEverywhere", "false");
|
||||||
|
|
||||||
|
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
|
||||||
|
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
|
||||||
|
_requiredigit = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireDigit", "true");
|
||||||
|
_requireupper = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireUppercase", "true");
|
||||||
|
_requirelower = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireLowercase", "true");
|
||||||
|
_requirepunctuation = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", "true");
|
||||||
|
|
||||||
|
_maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5");
|
||||||
|
_lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString();
|
||||||
|
|
||||||
|
LoadExternalLoginSettings(settings);
|
||||||
|
|
||||||
|
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
|
||||||
|
_togglesecret = SharedLocalizer["ShowPassword"];
|
||||||
|
_issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name);
|
||||||
|
_audience = SettingService.GetSetting(settings, "JwtOptions:Audience", "");
|
||||||
|
_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<UserRole> Search(string search)
|
private void LoadExternalLoginSettings(Dictionary<string, string> settings)
|
||||||
{
|
{
|
||||||
var results = allroles.Where(item => item.Role.Name == RoleNames.Registered || (item.Role.Name == RoleNames.Host && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)));
|
_provider = SettingService.GetSetting(settings, "ExternalLogin:Provider", "<Custom>");
|
||||||
|
_providerurl = SettingService.GetSetting(settings, "ExternalLogin:ProviderUrl", "");
|
||||||
if (string.IsNullOrEmpty(_search))
|
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
|
||||||
{
|
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
|
||||||
results = results.Where(item =>
|
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
|
||||||
(
|
_metadataurl = SettingService.GetSetting(settings, "ExternalLogin:MetadataUrl", "");
|
||||||
item.User.Username.Contains(search, StringComparison.OrdinalIgnoreCase) ||
|
_authorizationurl = SettingService.GetSetting(settings, "ExternalLogin:AuthorizationUrl", "");
|
||||||
item.User.Email.Contains(search, StringComparison.OrdinalIgnoreCase) ||
|
_tokenurl = SettingService.GetSetting(settings, "ExternalLogin:TokenUrl", "");
|
||||||
item.User.DisplayName.Contains(search, StringComparison.OrdinalIgnoreCase)
|
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
|
||||||
)
|
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
|
||||||
);
|
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
|
||||||
}
|
_toggleclientsecret = SharedLocalizer["ShowPassword"];
|
||||||
return results.ToList();
|
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
|
||||||
|
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
|
||||||
|
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
|
||||||
|
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
||||||
|
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||||
|
_reviewclaims = SettingService.GetSetting(settings, "ExternalLogin:ReviewClaims", "false");
|
||||||
|
_externalloginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external");
|
||||||
|
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
|
||||||
|
_nameclaimtype = SettingService.GetSetting(settings, "ExternalLogin:NameClaimType", "name");
|
||||||
|
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
|
||||||
|
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
|
||||||
|
_roleclaimmappings = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimMappings", "");
|
||||||
|
_synchronizeroles = SettingService.GetSetting(settings, "ExternalLogin:SynchronizeRoles", "false");
|
||||||
|
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
|
||||||
|
_savetokens = SettingService.GetSetting(settings, "ExternalLogin:SaveTokens", "false");
|
||||||
|
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
||||||
|
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||||
|
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
||||||
|
_allowhostrole = SettingService.GetSetting(settings, "ExternalLogin:AllowHostRole", "false");
|
||||||
|
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnSearch()
|
private async Task LoadUsersAsync(bool load)
|
||||||
{
|
{
|
||||||
userroles = Search(_search);
|
if (load)
|
||||||
await UpdateSettingsAsync();
|
{
|
||||||
|
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
var hosts = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Host);
|
||||||
|
users.AddRange(hosts);
|
||||||
|
users = users.OrderBy(u => u.User.DisplayName).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DeleteUser(UserRole UserRole)
|
private async Task DeleteUser(UserRole UserRole)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
|
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
|
user.IsDeleted = true;
|
||||||
await logger.LogInformation("User Deleted {User}", UserRole.User);
|
await UserService.UpdateUserAsync(user);
|
||||||
StateHasChanged();
|
await logger.LogInformation("User Soft Deleted {User}", user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var userrole = await UserRoleService.GetUserRoleAsync(UserRole.UserRoleId);
|
||||||
|
userrole.ExpiryDate = DateTime.UtcNow;
|
||||||
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
|
AddModuleMessage(Localizer["Success.DeleteUser"], MessageType.Success);
|
||||||
|
await LoadUsersAsync(true);
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
|
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
|
||||||
AddModuleMessage(ex.Message, MessageType.Error);
|
AddModuleMessage(Localizer["Error.DeleteUser"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string settingSearch = "AU-search";
|
private async Task SaveSiteSettings()
|
||||||
|
|
||||||
private async Task LoadSettingsAsync()
|
|
||||||
{
|
{
|
||||||
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
try
|
||||||
_search = SettingService.GetSetting(settings, settingSearch, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateSettingsAsync()
|
|
||||||
{
|
{
|
||||||
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
|
var site = PageState.Site;
|
||||||
SettingService.SetSetting(settings, settingSearch, _search);
|
site.AllowRegistration = bool.Parse(_allowregistration);
|
||||||
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
|
await SiteService.UpdateSiteAsync(site);
|
||||||
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||||
|
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:LogoutEverywhere", _logouteverywhere, false);
|
||||||
|
|
||||||
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireDigit", _requiredigit, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireUppercase", _requireupper, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireLowercase", _requirelower, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", _requirepunctuation, true);
|
||||||
|
|
||||||
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true);
|
||||||
|
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:Provider", _provider, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderType", _providertype, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderName", _providername, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:Authority", _authority, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:MetadataUrl", _metadataurl, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthorizationUrl", _authorizationurl, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:TokenUrl", _tokenurl, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:UserInfoUrl", _userinfourl, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ReviewClaims", _reviewclaims, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:NameClaimType", _nameclaimtype, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimMappings", _roleclaimmappings, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:SynchronizeRoles", _synchronizeroles, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:SaveTokens", _savetokens, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:AllowHostRole", _allowhostrole, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
||||||
|
|
||||||
|
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||||
|
await SettingService.ClearSiteSettingsCacheAsync();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_secret))
|
||||||
|
{
|
||||||
|
SiteState.AuthorizationToken = await UserService.GetTokenAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await ScrollToPageTop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProviderChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_provider = (string)e.Value;
|
||||||
|
var provider = Shared.ExternalLoginProviders.Providers.FirstOrDefault(item => item.Name == _provider);
|
||||||
|
if (provider != null)
|
||||||
|
{
|
||||||
|
LoadExternalLoginSettings(provider.Settings);
|
||||||
|
}
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProviderTypeChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_providertype = (string)e.Value;
|
||||||
|
if (string.IsNullOrEmpty(_providername))
|
||||||
|
{
|
||||||
|
if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||||
|
{
|
||||||
|
_scopes = "openid,profile,email";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_scopes = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CreateToken()
|
||||||
|
{
|
||||||
|
_token = await UserService.GetPersonalAccessTokenAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleClientSecret()
|
||||||
|
{
|
||||||
|
if (_clientsecrettype == "password")
|
||||||
|
{
|
||||||
|
_clientsecrettype = "text";
|
||||||
|
_toggleclientsecret = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_clientsecrettype = "password";
|
||||||
|
_toggleclientsecret = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleSecret()
|
||||||
|
{
|
||||||
|
if (_secrettype == "password")
|
||||||
|
{
|
||||||
|
_secrettype = "text";
|
||||||
|
_togglesecret = SharedLocalizer["HidePassword"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_secrettype = "password";
|
||||||
|
_togglesecret = SharedLocalizer["ShowPassword"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SortTable(string columnName)
|
||||||
|
{
|
||||||
|
if (columnName != activeSortColumn)
|
||||||
|
{
|
||||||
|
users = users.OrderBy(x => x.User.GetType().GetProperty(columnName)?.GetValue(x.User)).ToList();
|
||||||
|
isSortedAscending = true;
|
||||||
|
activeSortColumn = columnName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (isSortedAscending)
|
||||||
|
{
|
||||||
|
users = users.OrderByDescending(x => x.User.GetType().GetProperty(columnName)?.GetValue(x.User)).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
users = users.OrderBy(x => x.User.GetType().GetProperty(columnName)?.GetValue(x.User)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
isSortedAscending = !isSortedAscending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string SetSortIcon(string columnName)
|
||||||
|
{
|
||||||
|
if (activeSortColumn != columnName)
|
||||||
|
{
|
||||||
|
return "app-fas pe-3 ";
|
||||||
|
}
|
||||||
|
if (isSortedAscending)
|
||||||
|
{
|
||||||
|
return "app-fas oi oi-sort-ascending";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "app-fas oi oi-sort-descending";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,20 +12,16 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<table class="table table-borderless">
|
<div class="container">
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td width="30%">
|
<Label Class="col-sm-3" For="user" HelpText="The user you are assigning roles to" ResourceKey="User">User: </Label>
|
||||||
<Label For="user" HelpText="The user you are assigning roles to" ResourceKey="User">User: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<input id="user" class="form-control" @bind="@name" disabled />
|
<input id="user" class="form-control" @bind="@name" disabled />
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="role" HelpText="Select a role" ResourceKey="Role">Role: </Label>
|
||||||
<Label For="role" HelpText="Select a role" ResourceKey="Role">Role: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<select id="role" class="form-select" @bind="@roleid">
|
<select id="role" class="form-select" @bind="@roleid">
|
||||||
<option value="-1"><@Localizer["Role.Select"]></option>
|
<option value="-1"><@Localizer["Role.Select"]></option>
|
||||||
@foreach (Role role in roles)
|
@foreach (Role role in roles)
|
||||||
@@ -33,28 +29,26 @@ else
|
|||||||
<option value="@(role.RoleId)">@role.Name</option>
|
<option value="@(role.RoleId)">@role.Name</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
<div class="row mb-1 align-items-center">
|
||||||
<td>
|
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||||
<Label For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
<div class="col-sm-9">
|
||||||
</td>
|
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||||
<td>
|
</div>
|
||||||
<input id="effectiveDate" class="form-control" @bind="@effectivedate" />
|
</div>
|
||||||
</td>
|
<div class="row mb-1 align-items-center">
|
||||||
</tr>
|
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||||
<tr>
|
<div class="col-sm-9">
|
||||||
<td>
|
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||||
<Label For="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
|
||||||
<input id="expiryDate" class="form-control" @bind="@expirydate" />
|
</div>
|
||||||
</td>
|
<br />
|
||||||
</tr>
|
<br />
|
||||||
</table>
|
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveUserRole">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveUserRole">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
|
||||||
<hr class="app-rule" />
|
<hr class="app-rule" />
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<Pager Items="@userroles">
|
<Pager Items="@userroles">
|
||||||
@@ -66,10 +60,10 @@ else
|
|||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td>@context.Role.Name</td>
|
<td>@context.Role.Name</td>
|
||||||
<td>@context.EffectiveDate</td>
|
<td>@Utilities.UtcAsLocalDate(context.EffectiveDate)</td>
|
||||||
<td>@context.ExpiryDate</td>
|
<td>@Utilities.UtcAsLocalDate(context.ExpiryDate)</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.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>
|
||||||
@@ -81,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()
|
||||||
{
|
{
|
||||||
@@ -94,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)
|
||||||
@@ -116,8 +112,8 @@ else
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
|
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, userid);
|
||||||
userroles = userroles.Where(item => item.UserId == userid).ToList();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -132,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
|
||||||
@@ -159,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,9 +170,19 @@ else
|
|||||||
private async Task DeleteUserRole(int UserRoleId)
|
private async Task DeleteUserRole(int UserRoleId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
var userrole = await UserRoleService.GetUserRoleAsync(UserRoleId);
|
||||||
|
if (userrole.Role.Name == RoleNames.Registered)
|
||||||
|
{
|
||||||
|
userrole.ExpiryDate = DateTime.UtcNow;
|
||||||
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
||||||
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
|
await logger.LogInformation("User {Username} Removed From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
AddModuleMessage(Localizer["Success.User.Remove"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.User.Remove"], MessageType.Success);
|
||||||
await GetUserRoles();
|
await GetUserRoles();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
133
Oqtane.Client/Modules/Admin/Visitors/Detail.razor
Normal file
133
Oqtane.Client/Modules/Admin/Visitors/Detail.razor
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Visitors
|
||||||
|
@using System.Globalization
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject IVisitorService VisitorService
|
||||||
|
@inject IUserService UserService
|
||||||
|
@inject IStringLocalizer<Detail> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
@if (_initialized)
|
||||||
|
{
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="ip" HelpText="The last recorded IP address for this visitor" ResourceKey="IP">IP Address: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="ip" class="form-control" @bind="@_ip" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="language" HelpText="The last recorded language for this visitor" ResourceKey="Language">Language: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="language" class="form-control" @bind="@_language" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="useragent" HelpText="The last recorded user agent for this visitor" ResourceKey="UserAgent">User Agent: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="useragent" class="form-control" @bind="@_useragent" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="url" HelpText="The last recorded url for this visitor" ResourceKey="Url">Url: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="url" class="form-control" @bind="@_url" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="referrer" HelpText="The last recorded referrer for this visitor" ResourceKey="Referrer">Referrer: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="referrer" class="form-control" @bind="@_referrer" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (_user != string.Empty)
|
||||||
|
{
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="user" HelpText="The last recorded user associated with this visitor" ResourceKey="User">User: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="user" class="form-control" @bind="@_user" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="visits" HelpText="The total number of visits by this visitor all time" ResourceKey="Visits">Visits: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="visits" class="form-control" @bind="@_visits" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="visited" HelpText="The last recorded date/time when the visitor visited the site" ResourceKey="Visited">Visited: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="visited" class="form-control" @bind="@_visited" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="created" HelpText="The first recorded date/time when this visitor visited the site" ResourceKey="Created">Created: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="created" class="form-control" @bind="@_created" readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<NavLink class="btn btn-secondary" href="@CloseUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private bool _initialized = false;
|
||||||
|
private int _visitorId;
|
||||||
|
private string _ip = string.Empty;
|
||||||
|
private string _language = string.Empty;
|
||||||
|
private string _useragent = string.Empty;
|
||||||
|
private string _url = string.Empty;
|
||||||
|
private string _referrer = string.Empty;
|
||||||
|
private string _user = string.Empty;
|
||||||
|
private string _visits = string.Empty;
|
||||||
|
private string _visited = string.Empty;
|
||||||
|
private string _created = string.Empty;
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_visitorId = Int32.Parse(PageState.QueryString["id"]);
|
||||||
|
var visitor = await VisitorService.GetVisitorAsync(_visitorId);
|
||||||
|
if (visitor != null)
|
||||||
|
{
|
||||||
|
_ip = visitor.IPAddress;
|
||||||
|
_language = visitor.Language;
|
||||||
|
_useragent = visitor.UserAgent;
|
||||||
|
_url = visitor.Url;
|
||||||
|
_referrer = visitor.Referrer;
|
||||||
|
_visits = visitor.Visits.ToString();
|
||||||
|
_visited = UtcToLocal(visitor.VisitedOn).Value.ToString(CultureInfo.CurrentCulture);
|
||||||
|
_created = UtcToLocal(visitor.CreatedOn).Value.ToString(CultureInfo.CurrentCulture);
|
||||||
|
|
||||||
|
if (visitor.UserId != null)
|
||||||
|
{
|
||||||
|
var user = await UserService.GetUserAsync(visitor.UserId.Value, PageState.Site.SiteId);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
_user = user.DisplayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Error.LoadVisitor"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Loading Visitor {VisitorId} {Error}", _visitorId, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.LoadVisitor"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CloseUrl()
|
||||||
|
{
|
||||||
|
return (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : NavigateUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
224
Oqtane.Client/Modules/Admin/Visitors/Index.razor
Normal file
224
Oqtane.Client/Modules/Admin/Visitors/Index.razor
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
@namespace Oqtane.Modules.Admin.Visitors
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject IVisitorService VisitorService
|
||||||
|
@inject ISiteService SiteService
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
@if (_visitors == null)
|
||||||
|
{
|
||||||
|
<p><em>@SharedLocalizer["Loading"]</em></p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<TabStrip>
|
||||||
|
<TabPanel Name="Visitors" Heading="Visitors" ResourceKey="Visitors">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<select id="type" class="form-select custom-select" value="@_type" @onchange="(e => TypeChanged(e))">
|
||||||
|
<option value="visitors">@Localizer["AllVisitors"]</option>
|
||||||
|
<option value="users">@Localizer["UsersOnly"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<select id="days" class="form-select custom-select" value="@_days" @onchange="(e => DaysChanged(e))">
|
||||||
|
<option value="1">@Localizer["PastDay"]</option>
|
||||||
|
<option value="7">@Localizer["PastWeek"]</option>
|
||||||
|
<option value="30">@Localizer["PastMonth"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<Pager Items="@_visitors" CurrentPage="@_page.ToString()" OnPageChange="OnPageChange">
|
||||||
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
|
<th>@Localizer["IP"]</th>
|
||||||
|
<th>@Localizer["User"]</th>
|
||||||
|
<th>@Localizer["Language"]</th>
|
||||||
|
<th>@Localizer["Visits"]</th>
|
||||||
|
<th>@Localizer["Visited"]</th>
|
||||||
|
<th>@Localizer["Created"]</th>
|
||||||
|
</Header>
|
||||||
|
<Row>
|
||||||
|
<td><ActionLink Action="Detail" Text="Detail" Parameters="@($"id={context.VisitorId}")" ReturnUrl="@(NavigateUrl(PageState.Page.Path, $"type={_type}&days={_days}&page={_page}"))" ResourceKey="Details" /></td>
|
||||||
|
<td>@context.IPAddress</td>
|
||||||
|
<td>
|
||||||
|
@if (context.UserId != null)
|
||||||
|
{
|
||||||
|
@context.User.DisplayName
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>@context.Language</td>
|
||||||
|
<td>@context.Visits</td>
|
||||||
|
<td>@UtcToLocal(context.VisitedOn)</td>
|
||||||
|
<td>@UtcToLocal(context.CreatedOn)</td>
|
||||||
|
</Row>
|
||||||
|
</Pager>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="tracking" HelpText="Specify if visitor tracking is enabled" ResourceKey="Tracking">Tracking Enabled? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="tracking" class="form-select" @bind="@_tracking" >
|
||||||
|
<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="duration" HelpText="The duration of a browsing session considered to be a distinct visit (in minutes)" ResourceKey="Duration">Session Duration: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="duration" class="form-control" type="number" min="0" step="1" @bind="@_duration" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="filter" HelpText="Comma delimited list of terms which may exist in IP addresses, user agents, or languages identifying visitors which should not be tracked" ResourceKey="Filter">Filter: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<textarea id="filter" class="form-control" @bind="@_filter" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="retention" HelpText="Number of days of visitor activity to retain" ResourceKey="Retention">Retention: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="correlation" HelpText="Indicate if new visitors to this site should be correlated based on their IP Address" ResourceKey="Correlation">Correlate Visitors? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="correlation" class="form-select" @bind="@_correlation">
|
||||||
|
<option value="true">@SharedLocalizer["True"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["False"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
|
</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>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string _type = "visitors";
|
||||||
|
private int _days = 1;
|
||||||
|
private int _page = 1;
|
||||||
|
private List<Visitor> _visitors;
|
||||||
|
private string _tracking;
|
||||||
|
private int _duration = 5;
|
||||||
|
private string _filter = "";
|
||||||
|
private int _retention = 30;
|
||||||
|
private string _correlation = "true";
|
||||||
|
private string _robots = "";
|
||||||
|
|
||||||
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync()
|
||||||
|
{
|
||||||
|
if (PageState.QueryString.ContainsKey("type"))
|
||||||
|
{
|
||||||
|
_type = PageState.QueryString["type"];
|
||||||
|
}
|
||||||
|
if (PageState.QueryString.ContainsKey("days") && int.TryParse(PageState.QueryString["days"], out int days))
|
||||||
|
{
|
||||||
|
_days = days;
|
||||||
|
}
|
||||||
|
if (PageState.QueryString.ContainsKey("page") && int.TryParse(PageState.QueryString["page"], out int page))
|
||||||
|
{
|
||||||
|
_page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
await GetVisitors();
|
||||||
|
|
||||||
|
_tracking = PageState.Site.VisitorTracking.ToString();
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_duration = int.Parse(SettingService.GetSetting(settings, "VisitorDuration", "5"));
|
||||||
|
_filter = SettingService.GetSetting(settings, "VisitorFilter", Constants.DefaultVisitorFilter);
|
||||||
|
_retention = int.Parse(SettingService.GetSetting(settings, "VisitorRetention", "30"));
|
||||||
|
_correlation = SettingService.GetSetting(settings, "VisitorCorrelation", "true");
|
||||||
|
_robots = SettingService.GetSetting(settings, "Robots", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void TypeChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_type = e.Value.ToString();
|
||||||
|
await GetVisitors();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error On TypeChanged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void DaysChanged(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_days = int.Parse(e.Value.ToString());
|
||||||
|
await GetVisitors();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error On DateChanged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GetVisitors()
|
||||||
|
{
|
||||||
|
_visitors = await VisitorService.GetVisitorsAsync(PageState.Site.SiteId, DateTime.UtcNow.AddDays(-_days));
|
||||||
|
if (_type == "users")
|
||||||
|
{
|
||||||
|
_visitors = _visitors.Where(item => item.UserId != null).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveSiteSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var site = PageState.Site;
|
||||||
|
site.VisitorTracking = bool.Parse(_tracking);
|
||||||
|
await SiteService.UpdateSiteAsync(site);
|
||||||
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "VisitorDuration", _duration.ToString(), true);
|
||||||
|
settings = SettingService.SetSetting(settings, "VisitorFilter", _filter, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "VisitorRetention", _retention.ToString(), true);
|
||||||
|
settings = SettingService.SetSetting(settings, "VisitorCorrelation", _correlation, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "Robots", _robots, true);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
|
||||||
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPageChange(int page)
|
||||||
|
{
|
||||||
|
_page = page;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
@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)
|
||||||
{
|
{
|
||||||
|
@if (_visible)
|
||||||
|
{
|
||||||
<div class="app-actiondialog">
|
<div class="app-actiondialog">
|
||||||
<div class="modal" tabindex="-1" role="dialog">
|
<div class="modal" tabindex="-1" role="dialog">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
@@ -17,32 +22,87 @@
|
|||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@if (!string.IsNullOrEmpty(Action))
|
@if (!string.IsNullOrEmpty(Action))
|
||||||
{
|
{
|
||||||
<button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Localize(Action)</button>
|
<button type="button" class="@ConfirmClass" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
|
||||||
}
|
}
|
||||||
<button type="button" class="btn btn-secondary" @onclick="DisplayModal">@Localize("Cancel")</button>
|
<button type="button" class="@CancelClass" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (_authorized)
|
@if (_authorized)
|
||||||
{
|
{
|
||||||
if (Disabled)
|
if (Disabled)
|
||||||
{
|
{
|
||||||
<button class="@Class" disabled>@((MarkupString)_iconSpan) @Text</button>
|
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<button class="@Class" @onclick="DisplayModal">@((MarkupString)_iconSpan) @Text</button>
|
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (_visible)
|
||||||
|
{
|
||||||
|
<div class="app-actiondialog">
|
||||||
|
<div class="modal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form class="app-form-inline" method="post" @formname="@($"ActionDialogCloseForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">@Header</h5>
|
||||||
|
<button type="submit" class="btn-close" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>@Message</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
@if (!string.IsNullOrEmpty(Action))
|
||||||
|
{
|
||||||
|
<form method="post" @formname="@($"ActionDialogConfirmForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="Confirm" data-enhance>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<button type="submit" class="@ConfirmClass">@((MarkupString)_iconSpan) @Text</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
<form method="post" @formname="@($"ActionDialogCancelForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<button type="submit" class="@CancelClass">@SharedLocalizer["Cancel"]</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (_authorized)
|
||||||
|
{
|
||||||
|
if (Disabled)
|
||||||
|
{
|
||||||
|
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<form method="post" class="app-form-inline" @formname="@($"ActionDialogActionForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<button type="submit" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private bool _visible = false;
|
private bool _visible = false;
|
||||||
|
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
|
||||||
@@ -59,9 +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]
|
||||||
|
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
|
||||||
|
|
||||||
@@ -74,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();
|
||||||
@@ -82,30 +168,60 @@
|
|||||||
{
|
{
|
||||||
Text = Action;
|
Text = Action;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Class))
|
if (string.IsNullOrEmpty(Class))
|
||||||
{
|
{
|
||||||
Class = "btn btn-success";
|
Class = "btn btn-success";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(ConfirmClass))
|
||||||
|
{
|
||||||
|
ConfirmClass = Class;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(CancelClass))
|
||||||
|
{
|
||||||
|
CancelClass = "btn btn-secondary";
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(EditMode))
|
if (!string.IsNullOrEmpty(EditMode))
|
||||||
{
|
{
|
||||||
_editmode = bool.Parse(EditMode);
|
_editmode = bool.Parse(EditMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(IconName))
|
|
||||||
{
|
|
||||||
if (!IconName.Contains(" "))
|
|
||||||
{
|
|
||||||
IconName = "oi oi-" + IconName;
|
|
||||||
}
|
|
||||||
_iconSpan = $"<span class=\"{IconName}\"></span> ";
|
|
||||||
}
|
|
||||||
|
|
||||||
Text = Localize(nameof(Text), Text);
|
Text = Localize(nameof(Text), Text);
|
||||||
Header = Localize(nameof(Header), Header);
|
Header = Localize(nameof(Header), Header);
|
||||||
Message = Localize(nameof(Message), Message);
|
Message = Localize(nameof(Message), Message);
|
||||||
|
|
||||||
|
_openText = Text;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(IconName))
|
||||||
|
{
|
||||||
|
if (IconOnly)
|
||||||
|
{
|
||||||
|
_openText = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if IconName starts with "oi oi-"
|
||||||
|
bool startsWithOiOi = IconName.StartsWith("oi oi-");
|
||||||
|
|
||||||
|
if (!startsWithOiOi && !IconName.Contains(" "))
|
||||||
|
{
|
||||||
|
IconName = "oi oi-" + IconName;
|
||||||
|
}
|
||||||
|
_openIconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : " ")}";
|
||||||
|
_iconSpan = $"<span class=\"{IconName}\"></span> ";
|
||||||
|
}
|
||||||
|
|
||||||
|
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
|
||||||
_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()
|
||||||
@@ -138,10 +254,10 @@
|
|||||||
authorized = true;
|
authorized = true;
|
||||||
break;
|
break;
|
||||||
case SecurityAccessLevel.View:
|
case SecurityAccessLevel.View:
|
||||||
authorized = UserSecurity.IsAuthorized(PageState.User,PermissionNames.View, ModuleState.Permissions);
|
authorized = UserSecurity.IsAuthorized(PageState.User,PermissionNames.View, _permissions);
|
||||||
break;
|
break;
|
||||||
case SecurityAccessLevel.Edit:
|
case SecurityAccessLevel.Edit:
|
||||||
authorized = UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, ModuleState.Permissions);
|
authorized = UserSecurity.IsAuthorized(PageState.User,PermissionNames.Edit, _permissions);
|
||||||
break;
|
break;
|
||||||
case SecurityAccessLevel.Admin:
|
case SecurityAccessLevel.Admin:
|
||||||
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin);
|
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin);
|
||||||
@@ -157,12 +273,22 @@
|
|||||||
private void DisplayModal()
|
private void DisplayModal()
|
||||||
{
|
{
|
||||||
_visible = !_visible;
|
_visible = !_visible;
|
||||||
|
if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
|
||||||
|
{
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var parameters = new Dictionary<string, string>(PageState.QueryString);
|
||||||
|
if (parameters.ContainsKey("dialog")) parameters.Remove("dialog");
|
||||||
|
if (_visible) parameters.Add("dialog", Id);
|
||||||
|
NavigationManager.NavigateTo(PageState.Route.AbsolutePath + Utilities.CreateQueryString(parameters));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Confirm()
|
private void Confirm()
|
||||||
{
|
{
|
||||||
DisplayModal();
|
|
||||||
OnClick();
|
OnClick();
|
||||||
|
DisplayModal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,41 +8,60 @@
|
|||||||
{
|
{
|
||||||
if (Disabled)
|
if (Disabled)
|
||||||
{
|
{
|
||||||
<button class="@_classname" style="@_style" disabled>@((MarkupString)_iconSpan) @_text</button>
|
<NavLink class="@($"{_classname} disabled")" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if (OnClick == null)
|
||||||
{
|
{
|
||||||
<NavLink class="@_classname" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
<NavLink class="@_classname" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button" class="@_classname" style="@_style" onclick="@OnClick">@((MarkupString)_iconSpan) @_text</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _text = string.Empty;
|
private string _text = string.Empty;
|
||||||
private string _url = string.Empty;
|
private int _moduleId = -1;
|
||||||
|
private string _path = string.Empty;
|
||||||
private string _parameters = string.Empty;
|
private string _parameters = string.Empty;
|
||||||
private string _classname = "btn btn-primary";
|
private string _url = string.Empty;
|
||||||
private string _style = string.Empty;
|
private List<Permission> _permissions;
|
||||||
private bool _editmode = false;
|
private bool _editmode = false;
|
||||||
private bool _authorized = false;
|
private bool _authorized = false;
|
||||||
|
private string _classname = "btn btn-primary";
|
||||||
|
private string _style = string.Empty;
|
||||||
private string _iconSpan = string.Empty;
|
private string _iconSpan = string.Empty;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Action { get; set; } // required
|
public string Action { get; set; } // required
|
||||||
|
|
||||||
[Parameter]
|
|
||||||
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
|
|
||||||
|
|
||||||
[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 parameter 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 string Class { get; set; } // optional - defaults to primary if not specified
|
public string Path { get; set; } = null; // optional - allows the link to target a specific page
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Style { get; set; } // optional
|
public string Parameters { get; set; } // optional - querystring parameters should be in the form of "id=x&name=y"
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public Action OnClick { get; set; } = null; // optional - executes a method in the calling component
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public SecurityAccessLevel? Security { get; set; } // optional - can be used to explicitly specify SecurityAccessLevel
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Permissions { get; set; } // deprecated - use PermissionList instead
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public List<Permission> PermissionList { get; set; } // optional - can be used to specify permissions
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool Disabled { get; set; } // optional
|
public bool Disabled { get; set; } // optional
|
||||||
@@ -48,20 +69,41 @@
|
|||||||
[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 string EditMode { get; set; } // optional - specifies if an authorized user must be in edit mode to see the action - default is false.
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Class { get; set; } // optional - defaults to primary if not specified
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Style { get; set; } // optional
|
||||||
|
|
||||||
[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]
|
[Parameter]
|
||||||
public bool IconOnly { get; set; } // optional - specifies only icon in link
|
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()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
base.OnParametersSet();
|
base.OnParametersSet();
|
||||||
|
|
||||||
_text = Action;
|
|
||||||
if (!string.IsNullOrEmpty(Text))
|
if (!string.IsNullOrEmpty(Text))
|
||||||
{
|
{
|
||||||
_text = Text;
|
_text = Localize(nameof(Text), Text);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_text = Localize(nameof(Action), Action);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IconOnly && !string.IsNullOrEmpty(IconName))
|
if (IconOnly && !string.IsNullOrEmpty(IconName))
|
||||||
@@ -69,6 +111,18 @@
|
|||||||
_text = string.Empty;
|
_text = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_moduleId = ModuleState.ModuleId;
|
||||||
|
if (ModuleId != -1)
|
||||||
|
{
|
||||||
|
_moduleId = ModuleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
_path = PageState.Page.Path;
|
||||||
|
if (Path != null)
|
||||||
|
{
|
||||||
|
_path = Path;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Parameters))
|
if (!string.IsNullOrEmpty(Parameters))
|
||||||
{
|
{
|
||||||
_parameters = Parameters;
|
_parameters = Parameters;
|
||||||
@@ -91,16 +145,23 @@
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(IconName))
|
if (!string.IsNullOrEmpty(IconName))
|
||||||
{
|
{
|
||||||
if (!IconName.Contains(" "))
|
// Check if IconName starts with "oi oi-"
|
||||||
|
bool startsWithOiOi = IconName.StartsWith("oi oi-");
|
||||||
|
|
||||||
|
if (!startsWithOiOi && !IconName.Contains(" "))
|
||||||
{
|
{
|
||||||
IconName = "oi oi-" + IconName;
|
IconName = "oi oi-" + IconName;
|
||||||
}
|
}
|
||||||
_iconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : " ")}";
|
_iconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : " ")}";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_text = Localize(nameof(Text), _text);
|
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
|
||||||
_url = EditUrl(Action, _parameters);
|
|
||||||
|
_url = EditUrl(_path, _moduleId, Action, _parameters);
|
||||||
|
if (!string.IsNullOrEmpty(ReturnUrl))
|
||||||
|
{
|
||||||
|
_url += ((_url.Contains("?")) ? "&" : "?") + $"returnurl={WebUtility.UrlEncode(ReturnUrl)}";
|
||||||
|
}
|
||||||
_authorized = IsAuthorized();
|
_authorized = IsAuthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,10 +197,10 @@
|
|||||||
authorized = true;
|
authorized = true;
|
||||||
break;
|
break;
|
||||||
case SecurityAccessLevel.View:
|
case SecurityAccessLevel.View:
|
||||||
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, ModuleState.Permissions);
|
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, _permissions);
|
||||||
break;
|
break;
|
||||||
case SecurityAccessLevel.Edit:
|
case SecurityAccessLevel.Edit:
|
||||||
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.Permissions);
|
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _permissions);
|
||||||
break;
|
break;
|
||||||
case SecurityAccessLevel.Admin:
|
case SecurityAccessLevel.Admin:
|
||||||
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin);
|
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin);
|
||||||
|
|||||||
@@ -35,6 +35,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string Style { get; set; }
|
public string Style { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string DateTimeFormat { get; set; } = "MMM dd yyyy HH:mm:ss";
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
_text = string.Empty;
|
_text = string.Empty;
|
||||||
@@ -49,7 +52,7 @@
|
|||||||
|
|
||||||
if (CreatedOn != null)
|
if (CreatedOn != null)
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["On"]} <b>{CreatedOn.Value.ToString("MMM dd yyyy HH:mm:ss")}</b>";
|
_text += $" {Localizer["On"]} <b>{UtcToLocal(CreatedOn).Value.ToString(DateTimeFormat)}</ b >";
|
||||||
}
|
}
|
||||||
|
|
||||||
_text += "</p>";
|
_text += "</p>";
|
||||||
@@ -61,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("MMM dd yyyy HH:mm:ss")}</b>";
|
_text += $" {Localizer["On"]} <b>{UtcToLocal(ModifiedOn).Value.ToString(DateTimeFormat)}</ b >";
|
||||||
}
|
}
|
||||||
|
|
||||||
_text += "</p>";
|
_text += "</p>";
|
||||||
@@ -83,7 +86,7 @@
|
|||||||
|
|
||||||
if (DeletedOn != null)
|
if (DeletedOn != null)
|
||||||
{
|
{
|
||||||
_text += $" {Localizer["On"]} <b>{DeletedOn.Value.ToString("MMM dd yyyy HH:mm:ss")}</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,33 +1,37 @@
|
|||||||
@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">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@if (ShowFolders || FolderId <= 0)
|
<div class="container-fluid px-0">
|
||||||
|
@if (ShowFolders)
|
||||||
{
|
{
|
||||||
<div>
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
<select class="form-select" value="@FolderId" @onchange="(e => FolderChanged(e))">
|
<select class="form-select" value="@FolderId" @onchange="(e => FolderChanged(e))">
|
||||||
@if (string.IsNullOrEmpty(Folder))
|
|
||||||
{
|
|
||||||
<option value="-1"><@Localizer["Folder.Select"]></option>
|
<option value="-1"><@Localizer["Folder.Select"]></option>
|
||||||
}
|
|
||||||
@foreach (Folder folder in _folders)
|
@foreach (Folder folder in _folders)
|
||||||
{
|
{
|
||||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
@if (ShowFiles)
|
@if (ShowFiles)
|
||||||
{
|
{
|
||||||
<div>
|
<div class="row mt-1">
|
||||||
|
<div class="col">
|
||||||
<select class="form-select" value="@FileId" @onchange="(e => FileChanged(e))">
|
<select class="form-select" value="@FileId" @onchange="(e => FileChanged(e))">
|
||||||
<option value="-1"><@Localizer["File.Select"]></option>
|
<option value="-1"><@Localizer["File.Select"]></option>
|
||||||
@foreach (File file in _files)
|
@foreach (File file in _files)
|
||||||
@@ -36,10 +40,19 @@
|
|||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</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>
|
<div class="row 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 />
|
||||||
@@ -48,17 +61,39 @@
|
|||||||
{
|
{
|
||||||
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
||||||
}
|
}
|
||||||
<span id="@_progressinfoid"></span><progress id="@_progressbarid" style="width: 150px; visibility: hidden;"></progress>
|
</div>
|
||||||
<span class="float-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" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
|
<button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
|
||||||
}
|
}
|
||||||
</span>
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (ShowProgress)
|
||||||
|
{
|
||||||
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
<ModuleMessage Message="@_message" Type="@_messagetype"></ModuleMessage>
|
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>
|
||||||
@if (_image != string.Empty)
|
@if (_image != string.Empty)
|
||||||
{
|
{
|
||||||
@@ -71,7 +106,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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;
|
||||||
@@ -84,15 +119,22 @@
|
|||||||
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
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Folder { get; set; } // optional - for setting a specific folder by default
|
public int FolderId { get; set; } = -1; // optional - for setting a specific default folder by folderid
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int FolderId { get; set; } = -1; // optional - for setting a specific folderid by default
|
public string Folder { get; set; } = ""; // optional - for setting a specific default folder by folder path
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int FileId { get; set; } = -1; // optional - for selecting a specific file by default
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true
|
public bool ShowFiles { get; set; } = true; // optional - for indicating whether a list of files should be displayed - default is true
|
||||||
@@ -104,30 +146,86 @@
|
|||||||
public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true
|
public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int FileId { get; set; } = -1; // optional - for setting a specific file by default
|
public bool ShowImage { get; set; } = true; // optional - for indicating whether an image thumbnail should be displayed - default is true
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"
|
public bool ShowProgress { get; set; } = true; // optional - for indicating whether progress info should be displayed during upload - default is true
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
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
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
[Parameter]
|
||||||
|
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
||||||
|
|
||||||
|
[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]
|
||||||
|
public EventCallback<int> OnSelect { get; set; } // optional - executes a method in the calling component when a file is selected
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<int> OnDelete { get; set; } // optional - executes a method in the calling component when a file is deleted
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Folder))
|
protected override async Task OnParametersSetAsync()
|
||||||
{
|
{
|
||||||
_folders = new List<Folder> { new Folder { FolderId = -1, Name = Folder } };
|
// packages folder is a framework folder for uploading installable nuget packages
|
||||||
FolderId = -1;
|
if (Folder == Constants.PackagesFolder)
|
||||||
|
{
|
||||||
|
ShowFiles = false;
|
||||||
|
ShowFolders = false;
|
||||||
|
Filter = "nupkg";
|
||||||
|
ShowSuccess = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Folder) && Folder != Constants.PackagesFolder)
|
||||||
|
{
|
||||||
|
Folder folder = await FolderService.GetFolderAsync(ModuleState.SiteId, Folder);
|
||||||
|
if (folder != null)
|
||||||
|
{
|
||||||
|
FolderId = folder.FolderId;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
FolderId = -1;
|
||||||
|
_message = "Folder Path " + Folder + " Does Not Exist";
|
||||||
|
_messagetype = MessageType.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ShowFolders)
|
||||||
{
|
{
|
||||||
_folders = await FolderService.GetFoldersAsync(ModuleState.SiteId);
|
_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)
|
||||||
{
|
{
|
||||||
@@ -138,9 +236,12 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
_message = "FileId " + FileId.ToString() + " Does Not Exist";
|
||||||
|
_messagetype = MessageType.Error;
|
||||||
FileId = -1; // file does not exist
|
FileId = -1; // file does not exist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await SetImage();
|
await SetImage();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Filter))
|
if (!string.IsNullOrEmpty(Filter))
|
||||||
@@ -150,35 +251,37 @@
|
|||||||
|
|
||||||
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()
|
||||||
{
|
{
|
||||||
_haseditpermission = false;
|
_haseditpermission = false;
|
||||||
if (!string.IsNullOrEmpty(Folder))
|
if (Folder == Constants.PackagesFolder)
|
||||||
{
|
{
|
||||||
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
_haseditpermission = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
||||||
_files = await FileService.GetFilesAsync(Folder);
|
_files = new List<File>();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
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);
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Browse, folder.PermissionList))
|
||||||
|
{
|
||||||
_files = await FileService.GetFilesAsync(FolderId);
|
_files = await FileService.GetFilesAsync(FolderId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
_files = new List<File>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
_haseditpermission = false;
|
_haseditpermission = false;
|
||||||
_files = new List<File>();
|
_files = new List<File>();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (_filter != "*")
|
if (_filter != "*")
|
||||||
{
|
{
|
||||||
List<File> filtered = new List<File>();
|
List<File> filtered = new List<File>();
|
||||||
@@ -192,6 +295,7 @@
|
|||||||
_files = filtered;
|
_files = filtered;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task FolderChanged(ChangeEventArgs e)
|
private async Task FolderChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -203,12 +307,13 @@
|
|||||||
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)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
|
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
|
||||||
|
|
||||||
_message = Localizer["Error.File.Load"];
|
_message = Localizer["Error.File.Load"];
|
||||||
_messagetype = MessageType.Error;
|
_messagetype = MessageType.Error;
|
||||||
}
|
}
|
||||||
@@ -218,8 +323,11 @@
|
|||||||
{
|
{
|
||||||
_message = string.Empty;
|
_message = string.Empty;
|
||||||
FileId = int.Parse((string)e.Value);
|
FileId = int.Parse((string)e.Value);
|
||||||
|
|
||||||
await SetImage();
|
await SetImage();
|
||||||
|
#pragma warning disable CS0618
|
||||||
|
await OnSelect.InvokeAsync(FileId);
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
await OnSelectFile.InvokeAsync(FileId);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +338,7 @@
|
|||||||
if (FileId != -1)
|
if (FileId != -1)
|
||||||
{
|
{
|
||||||
_file = await FileService.GetFileAsync(FileId);
|
_file = await FileService.GetFileAsync(FileId);
|
||||||
if (_file != null && _file.ImageHeight != 0 && _file.ImageWidth != 0)
|
if (_file != null && ShowImage && _file.ImageHeight != 0 && _file.ImageWidth != 0)
|
||||||
{
|
{
|
||||||
var maxwidth = 200;
|
var maxwidth = 200;
|
||||||
var maxheight = 200;
|
var maxheight = 200;
|
||||||
@@ -246,59 +354,129 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
string restricted = "";
|
||||||
|
foreach (var upload in uploads)
|
||||||
|
{
|
||||||
|
var filename = upload.Split(':')[0];
|
||||||
|
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
|
||||||
|
if (!PageState.Site.UploadableFiles.Split(',').Contains(extension.ToLower()))
|
||||||
|
{
|
||||||
|
restricted += (restricted == "" ? "" : ",") + extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (restricted == "")
|
||||||
|
{
|
||||||
|
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string result;
|
// upload the files
|
||||||
if (!string.IsNullOrEmpty(Folder))
|
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)
|
||||||
{
|
{
|
||||||
result = await FileService.UploadFilesAsync(Folder, upload, _guid);
|
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
|
else
|
||||||
{
|
{
|
||||||
result = await FileService.UploadFilesAsync(FolderId, upload, _guid);
|
_uploading = false;
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == string.Empty)
|
if (success)
|
||||||
|
{
|
||||||
|
await logger.LogInformation("File Upload Succeeded {Files}", uploads);
|
||||||
|
if (ShowSuccess)
|
||||||
{
|
{
|
||||||
await logger.LogInformation("File Upload Succeeded {Files}", upload);
|
|
||||||
|
|
||||||
_message = Localizer["Success.File.Upload"];
|
_message = Localizer["Success.File.Upload"];
|
||||||
_messagetype = MessageType.Success;
|
_messagetype = MessageType.Success;
|
||||||
|
}
|
||||||
await GetFiles();
|
}
|
||||||
|
else
|
||||||
if (upload.Length == 1)
|
|
||||||
{
|
{
|
||||||
var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault();
|
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)
|
if (file != null)
|
||||||
{
|
{
|
||||||
FileId = file.FileId;
|
FileId = file.FileId;
|
||||||
await SetImage();
|
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();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
await logger.LogError("File Upload Failed For {Files}", result.Replace(",", ", "));
|
|
||||||
|
|
||||||
_message = Localizer["Error.File.Upload"];
|
|
||||||
_messagetype = MessageType.Error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
|
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
|
||||||
|
|
||||||
_message = Localizer["Error.File.Upload"];
|
_message = Localizer["Error.File.Upload"];
|
||||||
_messagetype = MessageType.Error;
|
_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
|
||||||
@@ -315,13 +493,21 @@
|
|||||||
{
|
{
|
||||||
await FileService.DeleteFileAsync(FileId);
|
await FileService.DeleteFileAsync(FileId);
|
||||||
await logger.LogInformation("File Deleted {File}", FileId);
|
await logger.LogInformation("File Deleted {File}", FileId);
|
||||||
|
await OnDelete.InvokeAsync(FileId);
|
||||||
|
|
||||||
|
if (ShowSuccess)
|
||||||
|
{
|
||||||
_message = Localizer["Success.File.Delete"];
|
_message = Localizer["Success.File.Delete"];
|
||||||
_messagetype = MessageType.Success;
|
_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)
|
||||||
@@ -335,5 +521,27 @@
|
|||||||
|
|
||||||
public int GetFileId() => FileId;
|
public int GetFileId() => FileId;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,17 +3,16 @@
|
|||||||
|
|
||||||
@if (!string.IsNullOrEmpty(HelpText))
|
@if (!string.IsNullOrEmpty(HelpText))
|
||||||
{
|
{
|
||||||
<span class="app-tooltip" data-tip="@((MarkupString)HelpText)">@((MarkupString)_openLabel)@ChildContent@((MarkupString)_closeLabel) <img src="images/help.png" /></span>
|
<span class="@_spanclass" data-tip="@((MarkupString)@_helptext)">
|
||||||
|
<label for="@For" class="@_labelclass">@ChildContent</label> <img src="images/help.png" />
|
||||||
|
</span>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@((MarkupString)_openLabel)@ChildContent@((MarkupString)_closeLabel)
|
<label for="@For" class="@_labelclass">@ChildContent</label>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _openLabel = string.Empty;
|
|
||||||
private string _closeLabel = "</label>";
|
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public RenderFragment ChildContent { get; set; }
|
public RenderFragment ChildContent { get; set; }
|
||||||
|
|
||||||
@@ -21,39 +20,37 @@ else
|
|||||||
public string For { get; set; } // optional - the id of the associated input control for accessibility
|
public string For { get; set; } // optional - the id of the associated input control for accessibility
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Class { get; set; } // optional - the class for the label ( ie. control-label )
|
public string Class { get; set; } // optional - CSS classes
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string HelpText { get; set; } // optional - tooltip for this label
|
public string HelpText { get; set; } // optional - tooltip for this label
|
||||||
|
|
||||||
|
private string _spanclass;
|
||||||
|
private string _labelclass;
|
||||||
|
private string _helptext = string.Empty;
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
base.OnParametersSet();
|
base.OnParametersSet();
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Class))
|
if (!string.IsNullOrEmpty(HelpText))
|
||||||
{
|
{
|
||||||
Class = "form-label";
|
_helptext = Localize(nameof(HelpText), HelpText);
|
||||||
}
|
_labelclass = "form-label";
|
||||||
|
|
||||||
_openLabel = "<label";
|
var spanclass = (!string.IsNullOrEmpty(Class)) ? " " + Class : "";
|
||||||
if (!string.IsNullOrEmpty(For))
|
_spanclass = "app-tooltip" + spanclass;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
_openLabel += " for=\"" + For + "\"";
|
var labelclass = (!string.IsNullOrEmpty(Class)) ? " " + Class : "";
|
||||||
|
_labelclass = "form-label" + labelclass;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Class))
|
|
||||||
{
|
|
||||||
_openLabel += " class=\"" + Class + "\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
_openLabel += ">";
|
|
||||||
|
|
||||||
var text = Localize("Text", String.Empty);
|
var text = Localize("Text", String.Empty);
|
||||||
if (text != String.Empty)
|
if (!string.IsNullOrEmpty(text))
|
||||||
{
|
{
|
||||||
ChildContent =@<text>@text</text>;
|
ChildContent =@<text>@text</text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
HelpText = Localize(nameof(HelpText), HelpText);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
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]
|
||||||
public string ResourceKey { get; set; }
|
public string ResourceKey { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string ResourceType { get; set; }
|
||||||
|
|
||||||
protected bool IsLocalizable { get; private set; }
|
protected bool IsLocalizable { get; private set; }
|
||||||
|
|
||||||
protected string Localize(string name) => _localizer?[name] ?? name;
|
protected string Localize(string name) => _localizer?[name] ?? name;
|
||||||
@@ -27,43 +30,32 @@ 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;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (value == String.Empty)
|
|
||||||
{
|
|
||||||
// Returns default property value (English version)
|
|
||||||
return propertyValue;
|
return propertyValue;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// return localized value
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
IsLocalizable = false;
|
IsLocalizable = false;
|
||||||
|
|
||||||
if (!String.IsNullOrEmpty(ResourceKey) && ModuleState?.ModuleType != null)
|
if (String.IsNullOrEmpty(ResourceType))
|
||||||
{
|
{
|
||||||
var moduleType = Type.GetType(ModuleState.ModuleType);
|
ResourceType = ModuleState?.ModuleType;
|
||||||
if (moduleType != null)
|
}
|
||||||
{
|
|
||||||
using (var scope = ServiceActivator.GetScope())
|
|
||||||
{
|
|
||||||
var localizerFactory = scope.ServiceProvider.GetService<IStringLocalizerFactory>();
|
|
||||||
_localizer = localizerFactory.Create(moduleType);
|
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty(ResourceKey) && !String.IsNullOrEmpty(ResourceType))
|
||||||
|
{
|
||||||
|
_localizer = LocalizerFactory.Create(ResourceType);
|
||||||
IsLocalizable = true;
|
IsLocalizable = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,26 @@
|
|||||||
@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" 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>
|
||||||
<br />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -25,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;
|
||||||
@@ -55,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,153 +1,386 @@
|
|||||||
@namespace Oqtane.Modules.Controls
|
@namespace Oqtane.Modules.Controls
|
||||||
@inherits ModuleControlBase
|
@inherits ModuleControlBase
|
||||||
|
@inject IStringLocalizerFactory LocalizerFactory
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
@typeparam TableItem
|
@typeparam TableItem
|
||||||
|
|
||||||
<p>
|
@if (ItemList != null)
|
||||||
@if (Toolbar == "Top")
|
{
|
||||||
|
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
|
||||||
{
|
{
|
||||||
<div class="mx-auto text-center">
|
@if (!string.IsNullOrEmpty(SearchProperties))
|
||||||
@if (_endPage > 1)
|
|
||||||
{
|
{
|
||||||
<button class="btn btn-secondary m-1" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="first" aria-hidden="true"></span></button>
|
<form autocomplete="off">
|
||||||
|
<div class="input-group my-3 @SearchBoxClass">
|
||||||
|
<input type="text" id="pagersearch" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" />
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
}
|
}
|
||||||
@if (_page > _maxPages)
|
|
||||||
|
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
|
||||||
{
|
{
|
||||||
<button class="btn btn-secondary m-1" @onclick=@(async () => SetPagerSize("back"))><span class="oi oi-media-skip-backward" title="back" aria-hidden="true"></span></button>
|
<ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
|
||||||
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
|
{
|
||||||
|
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
@if (_endPage > 1)
|
<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>
|
||||||
<button class="btn btn-secondary m-1" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></button>
|
</li>
|
||||||
@for (int i = _startPage; i <= _endPage; i++)
|
@for (int i = _startPage; i <= _endPage; i++)
|
||||||
{
|
{
|
||||||
var pager = i;
|
var pager = i;
|
||||||
<button class="btn @((pager == _page) ? "btn-primary" : "btn-link")" @onclick=@(async () => UpdateList(pager))>
|
if (pager == _page)
|
||||||
@pager
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
<button class="btn btn-secondary m-1" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></button>
|
|
||||||
}
|
|
||||||
@if (_endPage < _pages)
|
|
||||||
{
|
{
|
||||||
<button class="btn btn-secondary m-1" @onclick=@(async () => SetPagerSize("forward"))><span class="oi oi-media-skip-forward" title="forward" aria-hidden="true"></span></button>
|
<li class="page-item app-pager-pointer active">
|
||||||
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
@if (_endPage > 1)
|
else
|
||||||
{
|
{
|
||||||
<button class="btn btn-secondary m-1" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="last" aria-hidden="true"></span></button>
|
<li class="page-item app-pager-pointer">
|
||||||
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
@if (_endPage > 1)
|
}
|
||||||
|
<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)
|
||||||
{
|
{
|
||||||
<span class="btn btn-link disabled">Page @_page of @_pages</span>
|
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (!string.IsNullOrEmpty(SearchProperties))
|
||||||
|
{
|
||||||
|
<form method="post" autocomplete="off" @formname="PagerForm" @onsubmit="Search" data-enhance>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<div class="input-group my-3 @SearchBoxClass">
|
||||||
|
<input type="text" id="pagersearch" name="_search" class="form-control" placeholder=@string.Format(Localizer["SearchPlaceholder"], FormatSearchProperties()) @bind="@_search" />
|
||||||
|
<button type="submit" class="btn btn-primary">@SharedLocalizer["Search"]</button>
|
||||||
|
<a class="btn btn-secondary" href="@PageUrl(1, "")">@SharedLocalizer["Reset"]</a>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
}
|
}
|
||||||
@if (Format == "Table")
|
|
||||||
|
@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)
|
||||||
|
{
|
||||||
|
<div class="table-responsive">
|
||||||
<table class="@Class">
|
<table class="@Class">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>@Header</tr>
|
<tr class="@RowClass">@Header</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (var item in ItemList)
|
@foreach (var item in ItemList)
|
||||||
{
|
{
|
||||||
<tr>@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>
|
||||||
|
<tfoot>
|
||||||
|
<tr class="@RowClass">@Footer</tr>
|
||||||
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
@if (Format == "Grid")
|
@if (Format == "Grid" && Row != null)
|
||||||
{
|
{
|
||||||
|
int count = 0;
|
||||||
|
int rows = 0;
|
||||||
|
int cols = 0;
|
||||||
|
if (ItemList != null)
|
||||||
|
{
|
||||||
|
if (_columns == 0)
|
||||||
|
{
|
||||||
|
count = ItemList.Count();
|
||||||
|
rows = 1;
|
||||||
|
cols = count;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
count = (int)Math.Ceiling(ItemList.Count() / (decimal)_columns) * _columns;
|
||||||
|
rows = count / _columns;
|
||||||
|
cols = _columns;
|
||||||
|
}
|
||||||
|
}
|
||||||
<div class="@Class">
|
<div class="@Class">
|
||||||
<div class="row">@Header</div>
|
@for (int row = 0; row < rows; row++)
|
||||||
@foreach (var item in ItemList)
|
|
||||||
{
|
{
|
||||||
<div class="row">@Row(item)</div>
|
<div class="@RowClass">
|
||||||
@if (Detail != null)
|
@for (int col = 0; col < cols; col++)
|
||||||
{
|
{
|
||||||
<div class="row">@Detail(item)</div>
|
int index = (row * _columns) + col;
|
||||||
|
if (index < ItemList.Count())
|
||||||
|
{
|
||||||
|
<div class="@ColumnClass">@Row(ItemList.ElementAt(index))</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div> </div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (Toolbar == "Bottom")
|
</div>
|
||||||
{
|
|
||||||
<div class="mx-auto text-center">
|
|
||||||
@if (_endPage > 1)
|
|
||||||
{
|
|
||||||
<button class="btn btn-secondary mr-1" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="first" aria-hidden="true"></span></button>
|
|
||||||
}
|
}
|
||||||
@if (_page > _maxPages)
|
|
||||||
|
@if ((Toolbar == "Bottom" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
|
||||||
{
|
{
|
||||||
<button class="btn btn-secondary mr-1" @onclick=@(async () => SetPagerSize("back"))><span class="oi oi-media-skip-backward" title="back" aria-hidden="true"></span></button>
|
@if (PageState.RenderMode == RenderModes.Interactive || ModuleState.RenderMode == RenderModes.Interactive)
|
||||||
|
{
|
||||||
|
<ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
|
||||||
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
|
{
|
||||||
|
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
@if (_endPage > 1)
|
<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>
|
||||||
<button class="btn btn-secondary mr-1" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></button>
|
</li>
|
||||||
@for (int i = _startPage; i <= _endPage; i++)
|
@for (int i = _startPage; i <= _endPage; i++)
|
||||||
{
|
{
|
||||||
var pager = i;
|
var pager = i;
|
||||||
<button class="btn @((pager == _page) ? "btn-primary" : "btn-link")" @onclick=@(async () => UpdateList(pager))>
|
if (pager == _page)
|
||||||
@pager
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
<button class="btn btn-secondary mr-1" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></button>
|
|
||||||
}
|
|
||||||
@if (_endPage < _pages)
|
|
||||||
{
|
{
|
||||||
<button class="btn btn-secondary mr-1" @onclick=@(async () => SetPagerSize("forward"))><span class="oi oi-media-skip-forward" title="forward" aria-hidden="true"></span></button>
|
<li class="page-item app-pager-pointer active">
|
||||||
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
@if (_endPage > 1)
|
else
|
||||||
{
|
{
|
||||||
<button class="btn btn-secondary mr-1" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="last" aria-hidden="true"></span></button>
|
<li class="page-item app-pager-pointer">
|
||||||
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(pager))>@pager</a>
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
@if (_endPage > 1)
|
}
|
||||||
|
<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)
|
||||||
{
|
{
|
||||||
<span class="btn btn-link disabled">Page @_page of @_pages</span>
|
<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>
|
||||||
}
|
}
|
||||||
</div>
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
}
|
}
|
||||||
</p>
|
else
|
||||||
|
{
|
||||||
|
<ul class="pagination justify-content-@PaginationAlignment.ToLower() my-2">
|
||||||
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(1, _search)"><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
|
{
|
||||||
|
<li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(_startPage - 1, _search)"><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
<li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(((_page > 1) ? _page - 1 : _page), _search)"><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
@for (int i = _startPage; i <= _endPage; i++)
|
||||||
|
{
|
||||||
|
var pager = i;
|
||||||
|
if (pager == _page)
|
||||||
|
{
|
||||||
|
<li class="page-item app-pager-pointer active">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<li class="page-item app-pager-pointer">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(pager, _search)">@pager</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(((_page < _pages) ? _page + 1 : _page), _search)"><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
@if (_pages > _displayPages && _displayPages > 1)
|
||||||
|
{
|
||||||
|
<li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(_endPage + 1, _search)"><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
<li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
|
||||||
|
<a class="page-link shadow-none" href="@PageUrl(_pages, _search)"><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
|
||||||
|
</li>
|
||||||
|
<li class="page-item disabled">
|
||||||
|
<a class="page-link shadow-none" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
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;
|
||||||
private int _maxPages = 5;
|
private int _displayPages = 5;
|
||||||
private int _startPage = 0;
|
private int _startPage = 0;
|
||||||
private int _endPage = 0;
|
private int _endPage = 0;
|
||||||
|
private int _columns = 0;
|
||||||
|
private string _search = "";
|
||||||
|
|
||||||
|
private IEnumerable<TableItem> AllItems;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Format { get; set; }
|
public string Format { get; set; } // Table or Grid
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Toolbar { get; set; }
|
public string Toolbar { get; set; } // Top, Bottom or Both
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public RenderFragment Header { get; set; }
|
public RenderFragment Header { get; set; } = null; // only applicable to Table layouts
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public RenderFragment<TableItem> Row { get; set; }
|
public RenderFragment<TableItem> Row { get; set; } = null; // required
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public RenderFragment<TableItem> Detail { get; set; }
|
public RenderFragment Footer { get; set; } = null; // only applicable to Table layouts
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public IEnumerable<TableItem> Items { get; set; }
|
public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string PageSize { get; set; }
|
public IEnumerable<TableItem> Items { get; set; } // the IEnumerable data source
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string DisplayPages { get; set; }
|
public string PageSize { get; set; } // number of items to display on a page
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Class { get; set; }
|
public string Columns { get; set; } // only applicable to Grid layouts - default is zero indicating use responsive behavior
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string CurrentPage { get; set; } // sets the initial page to display
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string DisplayPages { get; set; } // maximum number of page numbers to display for user selection
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Class { get; set; } // class for the containing element - ie. <table> for Table or <div> for Grid
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string RowClass { get; set; } // class for row element - ie. <tr> for Table or <div> for Grid
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string ColumnClass { get; set; } // class for column element - only applicable to Grid format
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
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))
|
||||||
@@ -168,7 +401,45 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Class = "container";
|
Class = "container-fluid";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(RowClass))
|
||||||
|
{
|
||||||
|
if (Format == "Table")
|
||||||
|
{
|
||||||
|
RowClass = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RowClass = "row";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(ColumnClass))
|
||||||
|
{
|
||||||
|
if (Format == "Table")
|
||||||
|
{
|
||||||
|
ColumnClass = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ColumnClass = "col";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PageState.QueryString.ContainsKey("search"))
|
||||||
|
{
|
||||||
|
_search = PageState.QueryString["search"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(SearchProperties))
|
||||||
|
{
|
||||||
|
AllItems = Items; // only used in search
|
||||||
|
if (!string.IsNullOrEmpty(_search))
|
||||||
|
{
|
||||||
|
Search();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,88 +448,191 @@
|
|||||||
_maxItems = int.Parse(PageSize);
|
_maxItems = int.Parse(PageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(DisplayPages))
|
if (!string.IsNullOrEmpty(Columns))
|
||||||
{
|
{
|
||||||
_maxPages = int.Parse(DisplayPages);
|
_columns = int.Parse(Columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(DisplayPages))
|
||||||
|
{
|
||||||
|
_displayPages = int.Parse(DisplayPages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PageState.QueryString.ContainsKey("page") && int.TryParse(PageState.QueryString["page"], out int page))
|
||||||
|
{
|
||||||
|
_page = page;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(CurrentPage))
|
||||||
|
{
|
||||||
|
_page = int.Parse(CurrentPage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
_page = 1;
|
_page = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_page < 1) _page = 1;
|
||||||
|
|
||||||
_startPage = 0;
|
_startPage = 0;
|
||||||
_endPage = 0;
|
_endPage = 0;
|
||||||
|
|
||||||
if (Items != null)
|
if (Items != null)
|
||||||
{
|
{
|
||||||
ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
|
|
||||||
_pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems);
|
_pages = (int)Math.Ceiling(Items.Count() / (decimal)_maxItems);
|
||||||
|
if (_page > _pages)
|
||||||
|
{
|
||||||
|
_page = _pages;
|
||||||
|
}
|
||||||
|
SetPagerSize();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetPagerSize("forward");
|
public void SetPagerSize()
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateList(int currentPage)
|
|
||||||
{
|
{
|
||||||
ItemList = Items.Skip((currentPage - 1) * _maxItems).Take(_maxItems);
|
_startPage = ((_page - 1) / _displayPages) * _displayPages + 1;
|
||||||
_page = currentPage;
|
_endPage = _startPage + _displayPages - 1;
|
||||||
|
if (_endPage > _pages)
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetPagerSize(string direction)
|
|
||||||
{
|
|
||||||
if (direction == "forward")
|
|
||||||
{
|
|
||||||
if (_endPage + 1 < _pages)
|
|
||||||
{
|
|
||||||
_startPage = _endPage + 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_startPage = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_endPage + _maxPages < _pages)
|
|
||||||
{
|
|
||||||
_endPage = _startPage + _maxPages - 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
_endPage = _pages;
|
_endPage = _pages;
|
||||||
}
|
}
|
||||||
|
ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
OnPageChange?.Invoke(_page);
|
||||||
}
|
}
|
||||||
else if (direction == "back")
|
|
||||||
|
public void UpdateList(int page)
|
||||||
{
|
{
|
||||||
_endPage = _startPage - 1;
|
_page = page;
|
||||||
_startPage = _startPage - _maxPages;
|
SetPagerSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SkipPages(string direction)
|
||||||
|
{
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case "forward":
|
||||||
|
_page = _endPage + 1;
|
||||||
|
break;
|
||||||
|
case "back":
|
||||||
|
_page = _startPage - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetPagerSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NavigateToPage(string direction)
|
public void NavigateToPage(string direction)
|
||||||
{
|
{
|
||||||
if (direction == "next")
|
switch (direction)
|
||||||
{
|
{
|
||||||
|
case "next":
|
||||||
if (_page < _pages)
|
if (_page < _pages)
|
||||||
{
|
{
|
||||||
if (_page == _endPage)
|
|
||||||
{
|
|
||||||
SetPagerSize("forward");
|
|
||||||
}
|
|
||||||
_page += 1;
|
_page += 1;
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
else if (direction == "previous")
|
case "previous":
|
||||||
{
|
|
||||||
if (_page > 1)
|
if (_page > 1)
|
||||||
{
|
{
|
||||||
if (_page == _startPage)
|
|
||||||
{
|
|
||||||
SetPagerSize("back");
|
|
||||||
}
|
|
||||||
_page -= 1;
|
_page -= 1;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
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,87 +1,102 @@
|
|||||||
@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
|
||||||
|
|
||||||
@if (_permissions != null)
|
@if (_permissions != null)
|
||||||
{
|
{
|
||||||
<br />
|
<div class="container">
|
||||||
<table class="table table-borderless" style="width: 50%; min-width: 250px;">
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<table class="table table-borderless">
|
||||||
<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>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
@if (_users.Count != 0)
|
@if (_users.Count != 0)
|
||||||
{
|
{
|
||||||
<table class="table table-borderless" style="width: 50%; min-width: 250px;">
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table table-borderless">
|
||||||
<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>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
}
|
|
||||||
<table class="table table-borderless" style="width: 50%; min-width: 250px;">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td class="input-group">
|
|
||||||
<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 />
|
<br />
|
||||||
<ModuleMessage Type="MessageType.Error" Message="@_message" />
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-11">
|
||||||
|
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="_user" />
|
||||||
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
|
||||||
|
</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]
|
||||||
@@ -91,147 +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
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public List<Permission> PermissionList { get; set; }
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(PermissionNames))
|
|
||||||
{
|
|
||||||
_permissionnames = Shared.PermissionNames.View + "," + Shared.PermissionNames.Edit;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_permissionnames = PermissionNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId);
|
|
||||||
_roles.Insert(0, new Role { Name = RoleNames.Everyone });
|
|
||||||
|
|
||||||
_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))
|
if (!string.IsNullOrEmpty(Permissions))
|
||||||
{
|
{
|
||||||
// populate permissions
|
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
|
||||||
foreach (PermissionString permissionstring in UserSecurity.GetPermissionStrings(Permissions))
|
|
||||||
{
|
|
||||||
if (_permissions.Find(item => item.PermissionName == permissionstring.PermissionName) != null)
|
|
||||||
{
|
|
||||||
_permissions[_permissions.FindIndex(item => item.PermissionName == permissionstring.PermissionName)].Permissions = permissionstring.Permissions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (permissionstring.Permissions.Contains("["))
|
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
|
||||||
|
if (!UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
foreach (string user in permissionstring.Permissions.Split(new char[] { '[' }, StringSplitOptions.RemoveEmptyEntries))
|
_roles.RemoveAll(item => item.Name == RoleNames.Host);
|
||||||
{
|
|
||||||
if (user.Contains("]"))
|
|
||||||
{
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool? GetPermissionValue(string permissions, string securityKey)
|
// get permission names
|
||||||
|
if (string.IsNullOrEmpty(PermissionNames))
|
||||||
{
|
{
|
||||||
if ((";" + permissions + ";").Contains(";" + "!" + securityKey + ";"))
|
_permissionnames = new List<string>();
|
||||||
{
|
_permissionnames.Add(Shared.PermissionNames.View);
|
||||||
return false; // deny permission
|
_permissionnames.Add(Shared.PermissionNames.Edit);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if ((";" + permissions + ";").Contains(";" + securityKey + ";"))
|
_permissionnames = PermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize permissions
|
||||||
|
_permissions = new List<Permission>();
|
||||||
|
if (PermissionList != null && PermissionList.Any())
|
||||||
{
|
{
|
||||||
return true; // grant permission
|
foreach (var permission in PermissionList)
|
||||||
|
{
|
||||||
|
_permissions.Add(permission);
|
||||||
|
if (permission.UserId != null)
|
||||||
|
{
|
||||||
|
if (!_users.Any(item => item.UserId == permission.UserId.Value))
|
||||||
|
{
|
||||||
|
_users.Add(await UserService.GetUserAsync(permission.UserId.Value, ModuleState.SiteId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return null; // not specified
|
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 GetPermissionDisabled(string roleName)
|
private string GetPermissionName(string permissionName)
|
||||||
=> roleName == RoleNames.Admin
|
{
|
||||||
? true
|
return (permissionName.Contains(":")) ? permissionName.Split(':')[1] : permissionName;
|
||||||
: false;
|
}
|
||||||
|
|
||||||
|
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 != "")
|
||||||
|
{
|
||||||
|
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
|
||||||
|
if (permission != null)
|
||||||
|
{
|
||||||
|
isauthorized = permission.IsAuthorized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
|
||||||
|
if (permission != null)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PermissionChanged(bool? value, string permissionName, string roleName, int userId)
|
||||||
|
{
|
||||||
|
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);
|
|
||||||
if (user != null)
|
|
||||||
{
|
{
|
||||||
_users.Add(user);
|
_users.Add(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
else
|
||||||
{
|
{
|
||||||
_message = Localizer["Message.Username.DontExist"];
|
_message = Localizer["Message.Username.DontExist"];
|
||||||
}
|
}
|
||||||
}
|
_user.Clear();
|
||||||
|
|
||||||
_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();
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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(';').ToList();
|
}
|
||||||
ids.Remove("!" + RoleNames.Everyone); // remove deny all users
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
ids.Remove("!" + RoleNames.Registered); // remove deny registered users
|
{
|
||||||
permission.Permissions = string.Join(";", ids.ToArray());
|
// remove deny administrators and host users
|
||||||
_permissions[i] = permission;
|
permissions = _permissions.Where(item => !item.IsAuthorized &&
|
||||||
|
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)).ToList();
|
||||||
|
foreach (var permission in permissions)
|
||||||
|
{
|
||||||
|
_permissions.Remove(permission);
|
||||||
|
}
|
||||||
|
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)))
|
||||||
|
{
|
||||||
|
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Admin, null, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user