Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7df278e805 | ||
|
|
34325dde8c | ||
|
|
c7e3cc7e0a | ||
|
|
826876ff11 | ||
|
|
b031da6c6d | ||
|
|
43e15a1ef4 | ||
|
|
7c32188968 | ||
|
|
78d38bc4e2 | ||
|
|
f942631fd4 | ||
|
|
5d64c28e89 | ||
|
|
485c2f0a7d | ||
|
|
3d894d0e56 | ||
|
|
b265701844 | ||
|
|
468760bd31 | ||
|
|
f03744d99b | ||
|
|
26e78a1381 | ||
|
|
44aeb323d9 | ||
|
|
503298a33f | ||
|
|
58b456d5d5 | ||
|
|
d370cac14c | ||
|
|
b29c19f5e0 | ||
|
|
a3ba0cf6d5 | ||
|
|
8bb089fe07 | ||
|
|
7ba66f6386 | ||
|
|
b537f9b20a | ||
|
|
7abe8d71a8 | ||
|
|
3e01231874 | ||
|
|
fb8e23dc4b | ||
|
|
bf0d6cad2c | ||
|
|
5a9d36341c | ||
|
|
c7bf0b24a7 | ||
|
|
d69a505928 | ||
|
|
7980899f37 | ||
|
|
91d245a414 | ||
|
|
5db8d45f4d | ||
|
|
ee4f09845c | ||
|
|
01a717f143 | ||
|
|
efd22df657 | ||
|
|
2948e0aa4f | ||
|
|
b253683b3c | ||
|
|
189ebd4201 | ||
|
|
f1810e47e0 | ||
|
|
37f7924524 | ||
|
|
6427a5e5f1 | ||
|
|
96e11cb727 | ||
|
|
a5f9157217 | ||
|
|
133066c900 | ||
|
|
cf75623184 | ||
|
|
fecae8d0a3 | ||
|
|
f1d8220ab7 | ||
|
|
d51e8d1d1d | ||
|
|
9a761d2a77 | ||
|
|
407d018067 | ||
|
|
028b8b606a | ||
|
|
ad0e86d725 | ||
|
|
8aeed8b033 | ||
|
|
989c73f1f1 | ||
|
|
f6a3a04dba | ||
|
|
8ff11ccc88 | ||
|
|
5d145d0dd1 | ||
|
|
275ac445d0 | ||
|
|
7fc364cb63 | ||
|
|
93868fd905 | ||
|
|
a862486ec3 | ||
|
|
14c9b70149 | ||
|
|
b60c655b98 | ||
|
|
fe5475d5ec | ||
|
|
955a46ca60 | ||
|
|
0d0c88ceaa | ||
|
|
fb008569ab | ||
|
|
295c9c8617 | ||
|
|
658e43ab54 | ||
|
|
41b1227b17 | ||
|
|
68c7a41906 | ||
|
|
18f6d4cb16 | ||
|
|
7c60d7932a | ||
|
|
b9182a1fc7 | ||
|
|
e45d35ba60 | ||
|
|
8b73c702c3 | ||
|
|
8e744e860d | ||
|
|
94391d4cde | ||
|
|
4e73c56afe | ||
|
|
5606414f89 | ||
|
|
8ebb1a7d7f | ||
|
|
586043e00b | ||
|
|
e70c51a1f0 | ||
|
|
ea8f8de391 | ||
|
|
0332ef3298 | ||
|
|
751b0aa073 | ||
|
|
965db42170 | ||
|
|
73f16692da | ||
|
|
aab80e67bd | ||
|
|
3e3b629aa2 | ||
|
|
adeec24252 | ||
|
|
ba0fdaef72 | ||
|
|
c357c77e7e | ||
|
|
702375856d | ||
|
|
c936d83b38 | ||
|
|
83d22c9ebf | ||
|
|
02f45f4e65 | ||
|
|
da2ba45456 | ||
|
|
0566f7c89b | ||
|
|
30a2e8b990 | ||
|
|
81b2bce385 | ||
|
|
a5347cfcac | ||
|
|
3422db31ee | ||
|
|
755880ec90 | ||
|
|
30273709ae | ||
|
|
17f4bbde20 | ||
|
|
575e3750f1 | ||
|
|
d7c8eced81 | ||
|
|
86fda62a48 | ||
|
|
a4ec20a7c8 | ||
|
|
90653c1472 | ||
|
|
30ec59140c | ||
|
|
f689d1688a | ||
|
|
57e545e7c4 | ||
|
|
d0ea8e3a81 | ||
|
|
c188d2bf65 | ||
|
|
75b3421222 | ||
|
|
c062bd63bb | ||
|
|
0c753e85a8 | ||
|
|
32770c02ad | ||
|
|
133dd55f4c | ||
|
|
d1e2db9cb8 | ||
|
|
52a02409f0 | ||
|
|
eb3377deb8 | ||
|
|
c06bcac9f3 | ||
|
|
f9f0523112 | ||
|
|
f7aa5d0871 | ||
|
|
9576f6807b | ||
|
|
efe50a65ee | ||
|
|
fb34dd6114 | ||
|
|
2ebff3a864 | ||
|
|
a3f70735ac | ||
|
|
e1fd915ecd | ||
|
|
0097674fc0 | ||
|
|
f6341de610 | ||
|
|
17d5fae7bb | ||
|
|
05eca83b15 | ||
|
|
6d004b2095 | ||
|
|
46327f19a1 | ||
|
|
d4c8ea9361 | ||
|
|
7af0ddc226 | ||
|
|
80ea1782f9 | ||
|
|
aa46b42356 | ||
|
|
3cd069ffb9 | ||
|
|
e621ad6d7c | ||
|
|
729d8fb435 | ||
|
|
de2ddbe8b5 | ||
|
|
57ddd82ef1 | ||
|
|
79f7ec2715 | ||
|
|
ab4c3bd2fc | ||
|
|
bd3a7e1068 | ||
|
|
aa33ead7b2 | ||
|
|
94cf2a80a5 | ||
|
|
2ee959a08e | ||
|
|
54b7591ca0 | ||
|
|
b3f0392e7d | ||
|
|
77e7350e96 | ||
|
|
9c87572f90 | ||
|
|
65765f6c2f | ||
|
|
785b312009 | ||
|
|
1d9a710e3b | ||
|
|
909bf811d1 | ||
|
|
90abe01286 | ||
|
|
2c13d74c5f | ||
|
|
5d975c5da4 | ||
|
|
35c1057527 | ||
|
|
3912181857 | ||
|
|
b9a01eb5d6 | ||
|
|
3027f0acb0 | ||
|
|
609c2a5ddb | ||
|
|
f76d648012 | ||
|
|
203bf92a0a | ||
|
|
3e8cc9e905 | ||
|
|
b9f32c1739 | ||
|
|
7e1c65f616 | ||
|
|
447ae93476 | ||
|
|
6df40f41ac | ||
|
|
3e0ef3d070 | ||
|
|
33a17a9a1b | ||
|
|
aed4bfb7f4 | ||
|
|
78f7c3e5bd | ||
|
|
d764047c1f | ||
|
|
65015ea7c7 | ||
|
|
6ef2d9cb9d | ||
|
|
c39fdab11d | ||
|
|
547261abb6 | ||
|
|
7696b7b769 | ||
|
|
585635494a | ||
|
|
a36e80099a | ||
|
|
f5f8a4f6fb | ||
|
|
e207bff91f | ||
|
|
a2c7c230ef | ||
|
|
83a12a9a26 | ||
|
|
096fe130f6 | ||
|
|
12d22f92b2 | ||
|
|
017628e2a6 | ||
|
|
332b9f5816 | ||
|
|
09a96c1118 | ||
|
|
82750ee8a2 | ||
|
|
4e89f27768 | ||
|
|
6f60235a3f | ||
|
|
42be851d64 | ||
|
|
6c32c210f8 | ||
|
|
aca665ae4e | ||
|
|
09bee30610 | ||
|
|
87d53f6f93 | ||
|
|
98cf7ec715 | ||
|
|
12e50d93df | ||
|
|
dfcdb1994b | ||
|
|
30a5b87370 | ||
|
|
465e2b1b6a | ||
|
|
1ae52bbdb1 | ||
|
|
d0024d397a | ||
|
|
0b2cdc4d56 | ||
|
|
92af6f71d9 | ||
|
|
8879b8498f | ||
|
|
ad7e0e0bf9 | ||
|
|
edb7398295 | ||
|
|
cfca28e25a | ||
|
|
a811e22f95 | ||
|
|
fbb2e6e261 | ||
|
|
c44b99d515 | ||
|
|
0f283e6cdf | ||
|
|
b7e8f796a3 | ||
|
|
d63507dd97 | ||
|
|
e1168b5107 | ||
|
|
cecb4b8336 | ||
|
|
5bbbf9d0e6 | ||
|
|
29775eef9a | ||
|
|
3f3e645c96 | ||
|
|
ebece3f961 | ||
|
|
4951d409db | ||
|
|
cf90511f3c | ||
|
|
4ec0e63d99 | ||
|
|
08843a9dd6 | ||
|
|
b3f229dc86 | ||
|
|
18b74e7a61 | ||
|
|
c60961cbb1 | ||
|
|
3338fc366d | ||
|
|
79cbf25b73 | ||
|
|
7963118032 | ||
|
|
35e6c938dc | ||
|
|
c632bc4555 | ||
|
|
7a34bc3eb0 | ||
|
|
6e0d41834c | ||
|
|
4bcbaf9113 | ||
|
|
5448a15691 | ||
|
|
c63feb3ff3 | ||
|
|
fb4d95672e | ||
|
|
bfb1658abe | ||
|
|
79e2c9175f | ||
|
|
b9c2d49a1c | ||
|
|
a25eb4ded6 | ||
|
|
3851de2774 | ||
|
|
0783b733b0 | ||
|
|
bb17feaa17 | ||
|
|
7623c8dcb7 | ||
|
|
1d7eb8a03b | ||
|
|
c5712f5ae0 | ||
|
|
00e5808a4c | ||
|
|
0a591564fd | ||
|
|
8448068d22 | ||
|
|
9fa36c3991 | ||
|
|
d949753d54 | ||
|
|
f6d2b08025 | ||
|
|
8a446de3e9 | ||
|
|
0196f46094 | ||
|
|
bd83b3bc8e | ||
|
|
89347abf53 | ||
|
|
a27f8ecfa9 | ||
|
|
75ca4920c5 | ||
|
|
324be07b85 | ||
|
|
282a5b30f9 | ||
|
|
b24acefa6f | ||
|
|
751b519167 | ||
|
|
330aec2bdd | ||
|
|
d5635c3a89 | ||
|
|
1e86d67bfe | ||
|
|
8ac9353c53 | ||
|
|
1557970170 | ||
|
|
508bbb37ce | ||
|
|
a90fc396a9 | ||
|
|
96627540da | ||
|
|
db6ebe0470 | ||
|
|
559251eb0d | ||
|
|
23396d1bd6 | ||
|
|
9d92a79a28 | ||
|
|
90a64a7403 | ||
|
|
f709cc332c | ||
|
|
c925a27870 | ||
|
|
d31403a918 | ||
|
|
f9051d768c | ||
|
|
06eb2adeaa | ||
|
|
12af3a5d10 | ||
|
|
8bf94d6ac6 | ||
|
|
15af5d2c2e | ||
|
|
60c5c5c723 | ||
|
|
f972da7442 | ||
|
|
948537cb75 | ||
|
|
ec5d0d00f5 | ||
|
|
0e7c454013 | ||
|
|
6e8e581ceb | ||
|
|
2ec435af41 | ||
|
|
5aa38c2e40 | ||
|
|
0ff3952a8b | ||
|
|
e32d2daa45 | ||
|
|
57780801aa | ||
|
|
ecdfc68170 | ||
|
|
3b29678ef1 | ||
|
|
6c54b38cdf | ||
|
|
dfcabc5a36 | ||
|
|
5c8cf2ebbd | ||
|
|
a9d41217bd | ||
|
|
75f44a821c | ||
|
|
0ab16764d1 | ||
|
|
b6e7e03c95 | ||
|
|
718c1a393d | ||
|
|
50f3316088 | ||
|
|
2727651897 | ||
|
|
e72646a4d4 | ||
|
|
8ef8d546c5 | ||
|
|
a8b2d7a6bd | ||
|
|
0a1074ca6e | ||
|
|
019d7c92c3 | ||
|
|
cb653e03f2 | ||
|
|
a6c8833f65 | ||
|
|
f00b39d154 | ||
|
|
39597e04ac | ||
|
|
1610eff0e9 | ||
|
|
c944f9fdff | ||
|
|
9b4d4c5953 | ||
|
|
a8f09011e9 | ||
|
|
2e1675969a | ||
|
|
a71973cc80 | ||
|
|
608ec63899 | ||
|
|
52bdba8e85 | ||
|
|
f5b11b15ce | ||
|
|
3359559ad2 | ||
|
|
b4aa464473 | ||
|
|
dbe31a86b7 | ||
|
|
bd283ff4e2 | ||
|
|
c020ce8895 | ||
|
|
f3562525db | ||
|
|
cc89c35c8d | ||
|
|
2457084db2 | ||
|
|
508a2d7103 | ||
|
|
3d7fef67d8 | ||
|
|
d63d445b7d | ||
|
|
8d3fe29c68 | ||
|
|
ad3ecbfd96 | ||
|
|
51ec43e7bc | ||
|
|
88dfc57e7e | ||
|
|
7409c366f6 | ||
|
|
b99b2a6d66 | ||
|
|
fa21b00bb6 | ||
|
|
f061b5e12a | ||
|
|
8ca642edc8 | ||
|
|
c3909edc5e | ||
|
|
f4cf09d394 | ||
|
|
6aa4f9fb86 | ||
|
|
a31545824e | ||
|
|
a94cfd5b91 | ||
|
|
6d6d51310e | ||
|
|
c9cf2971de | ||
|
|
54c481e3d8 | ||
|
|
8b4fbd266d | ||
|
|
3c5241b007 | ||
|
|
42a1f94931 | ||
|
|
cae6f38b80 | ||
|
|
25722e5e9d | ||
|
|
1d339ec17e | ||
|
|
ec534c8cca | ||
|
|
cab285c34d | ||
|
|
975993dd6d | ||
|
|
43084eb664 | ||
|
|
0e770ac3b3 | ||
|
|
206abc8c68 | ||
|
|
62b13cc343 | ||
|
|
7822dd3b75 | ||
|
|
a214cd64b6 | ||
|
|
8839a8ea39 | ||
|
|
a4169f3da7 | ||
|
|
19d59b3ab4 | ||
|
|
27b2e2384c | ||
|
|
49d2cbc2c1 | ||
|
|
bf2b3b40cd | ||
|
|
1541c6ca23 | ||
|
|
42c1ff94e2 | ||
|
|
53aa5ebc17 | ||
|
|
0f43818757 | ||
|
|
f3c5b1eb07 | ||
|
|
517d1bb7fa | ||
|
|
6ee0919ead | ||
|
|
13eb1a58e0 | ||
|
|
08f9c09484 | ||
|
|
3f3abe26ee | ||
|
|
e31c5b6d42 | ||
|
|
7bf77fa7cb | ||
|
|
a2662f1366 | ||
|
|
116cf6aa21 | ||
|
|
79bb6d0c55 | ||
|
|
c157ecec38 | ||
|
|
28893cf637 | ||
|
|
73ffc99f37 | ||
|
|
35802c7c62 | ||
|
|
bc44fdf89c | ||
|
|
030c3dd587 | ||
|
|
498be2759e | ||
|
|
649d00733a | ||
|
|
8a7a4f3033 | ||
|
|
b6ce81dc79 | ||
|
|
490c417cee | ||
|
|
2dfeb3924b | ||
|
|
bff96f6ae2 | ||
|
|
4801e26b2f | ||
|
|
027997f7b3 | ||
|
|
76aaefbde1 | ||
|
|
a01bc0f325 | ||
|
|
f6f4f5660e | ||
|
|
956925889a | ||
|
|
0bd49326d8 | ||
|
|
64c4e8eca8 | ||
|
|
55fc342f6d | ||
|
|
3ae0d96760 | ||
|
|
fc1e55cc19 | ||
|
|
58fc810a43 | ||
|
|
3c477e9783 | ||
|
|
f2247ed86d | ||
|
|
0d9a9d1ed8 | ||
|
|
ca5a579e22 | ||
|
|
53d97a593f | ||
|
|
3641a8f389 | ||
|
|
1dea1bd09d | ||
|
|
3db265d57f | ||
|
|
38a32824ea | ||
|
|
b0c94f7ff2 | ||
|
|
2415aa3d2b | ||
|
|
1af1844f37 | ||
|
|
75bb247c2b | ||
|
|
5a1b517733 | ||
|
|
0fbc8754f7 | ||
|
|
aaca60b676 | ||
|
|
a929d1e3a4 | ||
|
|
4a7fa7c396 | ||
|
|
45674806d7 | ||
|
|
8905bc7dfe | ||
|
|
dd6aec1ba9 | ||
|
|
aca5fa4d0a | ||
|
|
0d1f45b8a2 | ||
|
|
3f9ae57144 | ||
|
|
45c2f1007b | ||
|
|
83adaa3074 | ||
|
|
1951b279c0 | ||
|
|
50406ad683 | ||
|
|
95740eaa01 | ||
|
|
08f2c1c735 | ||
|
|
32459ec405 | ||
|
|
c7ac0cbce1 | ||
|
|
711fb5754e | ||
|
|
24fffd8bfa | ||
|
|
52556aff68 | ||
|
|
c76959ff25 | ||
|
|
a141871e60 | ||
|
|
5e639a7c2d | ||
|
|
f9654d17d5 | ||
|
|
c89f3ba3da | ||
|
|
1dc5ae444e | ||
|
|
5db67e47a8 | ||
|
|
3877e5feb9 | ||
|
|
7456186265 | ||
|
|
952f7feb49 | ||
|
|
3983d750f3 | ||
|
|
b4f2a8ab5b | ||
|
|
e8a4554789 | ||
|
|
de3bc9cb4d | ||
|
|
e09f028cd1 | ||
|
|
ea9f597685 | ||
|
|
e538fb9514 | ||
|
|
32fed3d048 | ||
|
|
c6b6d1aa40 | ||
|
|
b9615ccbad | ||
|
|
0032a91f66 | ||
|
|
88f6076422 | ||
|
|
8e11ecf799 | ||
|
|
d04c5c9846 | ||
|
|
e46a66904f | ||
|
|
d038cb4a2d | ||
|
|
78e68c7fea | ||
|
|
700f345576 | ||
|
|
22067198ab | ||
|
|
21d6dfd790 | ||
|
|
e663ce038a | ||
|
|
367b7778fd | ||
|
|
989e0a5326 | ||
|
|
13940f0663 | ||
|
|
09e87f207c | ||
|
|
f9884b7782 | ||
|
|
cca5f81c65 | ||
|
|
c863428aa2 | ||
|
|
2104b853ef | ||
|
|
00a56f6aaa | ||
|
|
67e7a75b9c | ||
|
|
52375df783 | ||
|
|
77fad0e7c6 | ||
|
|
802341384e | ||
|
|
057ac4031e | ||
|
|
ea5d0999c4 | ||
|
|
7b21650003 | ||
|
|
1cfaddff9c | ||
|
|
7690d91771 | ||
|
|
6b7b66801b | ||
|
|
d671071758 | ||
|
|
e9c1c620eb | ||
|
|
b17a8757fb | ||
|
|
2b04cef50b | ||
|
|
16060108b4 | ||
|
|
0fb2f7fdd6 | ||
|
|
c002cf759e | ||
|
|
5fee0b05e0 | ||
|
|
48bc176bdf | ||
|
|
ec56dc73c4 | ||
|
|
83e9d681d7 | ||
|
|
e0b9e80698 | ||
|
|
449c02c42a | ||
|
|
ce89ff4833 | ||
|
|
1961e22cbd | ||
|
|
b95ee7cf4d | ||
|
|
14b2291590 | ||
|
|
954ec05d41 | ||
|
|
4c25b40b2d | ||
|
|
1792e3d884 | ||
|
|
3c35a6400b | ||
|
|
2a4ba31a9e | ||
|
|
99a8afd6c3 | ||
|
|
cc9ff4c8db | ||
|
|
ed4189cc5e | ||
|
|
6d66e60830 | ||
|
|
930f0a7f82 | ||
|
|
89347e2a56 | ||
|
|
09a08cd074 | ||
|
|
c344474d9e | ||
|
|
d13d169768 | ||
|
|
e3929973d1 | ||
|
|
9b428150a4 | ||
|
|
8ba565042f | ||
|
|
6df03294bd | ||
|
|
4a40ac3ced | ||
|
|
2f34d65164 | ||
|
|
7c68d6a710 | ||
|
|
55fb7a8f27 | ||
|
|
bb9fb1187e | ||
|
|
f0170c576c | ||
|
|
02caaafc52 | ||
|
|
168b17ac4e | ||
|
|
e3214c874e | ||
|
|
91444cce9b | ||
|
|
c952aaadec | ||
|
|
77ab5c1690 | ||
|
|
37857935f3 | ||
|
|
e5c7650fc8 | ||
|
|
f42e0fe507 | ||
|
|
58dbd78997 | ||
|
|
04b995e77f | ||
|
|
82aa3fb7ee | ||
|
|
ec0e88ad2e | ||
|
|
cac28fde4d | ||
|
|
e65a8c9017 | ||
|
|
4a26f80fb1 | ||
|
|
bca8a72a0f | ||
|
|
13be8160d5 | ||
|
|
8efea6c4e2 | ||
|
|
36bb5e099e | ||
|
|
b86a24d97a | ||
|
|
3febe80329 | ||
|
|
b11a2103a4 | ||
|
|
4a9d881e47 | ||
|
|
8e5c6af612 | ||
|
|
ca588a4b54 | ||
|
|
258032acb0 | ||
|
|
2c9c9c4356 | ||
|
|
88d4e8a4d9 | ||
|
|
4e2a8735bc | ||
|
|
b72d24f6d0 | ||
|
|
1f2fb2ccce | ||
|
|
5e2b423ef8 | ||
|
|
79e2001708 | ||
|
|
2ea96518ad | ||
|
|
2666001226 | ||
|
|
1bd30bc121 | ||
|
|
d6d8144476 | ||
|
|
68e7818c1d | ||
|
|
e0e5f4514b | ||
|
|
c2942fa269 | ||
|
|
6f31240c74 | ||
|
|
5124265142 | ||
|
|
f0bd5eaca9 | ||
|
|
8589f85907 | ||
|
|
66d739b3b1 | ||
|
|
2206e34ab6 | ||
|
|
cc1ca4a1bb | ||
|
|
c8b1ef8ea6 | ||
|
|
f836faad48 | ||
|
|
7e27b916aa | ||
|
|
ae9963c1f7 | ||
|
|
2b4f4c8d4d | ||
|
|
68104fe578 | ||
|
|
6f2c46a747 | ||
|
|
e7e1f1b667 | ||
|
|
7dc20616a7 | ||
|
|
b5b323000a | ||
|
|
19d204c7a8 | ||
|
|
672c279b0a | ||
|
|
669e31ea64 | ||
|
|
4ab72170e3 | ||
|
|
9ca39696b3 | ||
|
|
7c36279308 | ||
|
|
89e5b3a979 | ||
|
|
58782c9a89 | ||
|
|
c3824dcbd2 | ||
|
|
cead2bc00f | ||
|
|
0d5380466a | ||
|
|
58f6acbc48 | ||
|
|
db73fb4e37 | ||
|
|
a3d4c3d948 | ||
|
|
f9dc5c6f80 | ||
|
|
96b763fb89 | ||
|
|
98a75305e7 | ||
|
|
0d8739327e | ||
|
|
7dc4d9bb71 | ||
|
|
c814221185 | ||
|
|
f2f6b57944 | ||
|
|
f50c2af914 | ||
|
|
5b7442436d | ||
|
|
9cebee7347 | ||
|
|
f311359373 | ||
|
|
31b9970c04 | ||
|
|
baf0edc51e | ||
|
|
69a2a76b2e | ||
|
|
7fd894f33d | ||
|
|
3f366844e2 | ||
|
|
4156b6f932 | ||
|
|
ed801f7082 | ||
|
|
cedd10e067 | ||
|
|
86b756b6bc | ||
|
|
259a12acf9 | ||
|
|
902f86d363 | ||
|
|
d5fc75e345 | ||
|
|
5650bd829a | ||
|
|
f9ad46180e | ||
|
|
6e534d7543 | ||
|
|
9aa56a8e42 | ||
|
|
4ed67832d2 | ||
|
|
3409de9ed1 | ||
|
|
e73a44d15b | ||
|
|
5cf30f05fd | ||
|
|
63117e87e7 | ||
|
|
a8e520e7f0 | ||
|
|
e14bb16d60 | ||
|
|
b704d58f0f | ||
|
|
246e9a7764 | ||
|
|
1c7b63f583 | ||
|
|
e6476319bc | ||
|
|
c954934b33 | ||
|
|
a72a6886f6 | ||
|
|
a601ba542a | ||
|
|
f22f470ba4 | ||
|
|
3faeeac126 | ||
|
|
40be1d416b | ||
|
|
9ef82458b7 | ||
|
|
da99e363cd | ||
|
|
3110bb48fd | ||
|
|
08e2f2f273 | ||
|
|
6636d12215 | ||
|
|
5f243d173a | ||
|
|
5ea8d3a4c6 | ||
|
|
dad3fd22ec | ||
|
|
f381402e8f | ||
|
|
f010235ff2 | ||
|
|
09f6dcbde1 | ||
|
|
224b8e3492 | ||
|
|
28612bbc8a | ||
|
|
dfe4f7bb56 | ||
|
|
3f28c0dd22 | ||
|
|
959dd1a95a | ||
|
|
d5297cd1df | ||
|
|
95404f43b7 | ||
|
|
9838af5e56 | ||
|
|
dab656f8ca | ||
|
|
802eeb1e6b | ||
|
|
98dd28818a | ||
|
|
f705373cc4 | ||
|
|
5bc60a8dab | ||
|
|
1c8f783767 | ||
|
|
24d3075c85 | ||
|
|
c461adff11 | ||
|
|
b172f7207d | ||
|
|
ef31fffa40 | ||
|
|
426632a307 | ||
|
|
2c708b6df6 | ||
|
|
b555d0ccc8 | ||
|
|
e1724c8a12 | ||
|
|
cd35574c0b | ||
|
|
d016cbcffc | ||
|
|
7e4b672888 | ||
|
|
06d9abf6f6 | ||
|
|
a1a6dae5c6 | ||
|
|
c5f40f1be9 | ||
|
|
4358cb5e57 | ||
|
|
a591bf05e0 | ||
|
|
568b7538ad | ||
|
|
bbd7765432 | ||
|
|
558024785f | ||
|
|
76043cc5b6 | ||
|
|
583fca1814 | ||
|
|
806201b8cb | ||
|
|
24c89aeb64 | ||
|
|
8f0d88d9e0 | ||
|
|
e92d8dc127 | ||
|
|
8a99e8f796 | ||
|
|
c0246c6834 | ||
|
|
42ed92f346 | ||
|
|
df0bd90fae | ||
|
|
3453f94b43 | ||
|
|
ca537e266d | ||
|
|
8f3c948614 | ||
|
|
059743ad34 | ||
|
|
33ccf68e17 | ||
|
|
b6f22712f9 | ||
|
|
9e413b3961 | ||
|
|
5dd318a481 | ||
|
|
1783257c0e | ||
|
|
966852c212 | ||
|
|
07d6c1e514 | ||
|
|
09be915ea5 | ||
|
|
abd3c95dfa | ||
|
|
3fa61ac23c | ||
|
|
00bbc26698 | ||
|
|
3004f54b5d | ||
|
|
ef5b888779 | ||
|
|
835e33bf09 | ||
|
|
8ba3a05648 | ||
|
|
73ae73d4c3 | ||
|
|
76175a711a | ||
|
|
574acd6744 | ||
|
|
9e0e052bba | ||
|
|
ffafb30b9b | ||
|
|
9c4e402e6c | ||
|
|
f6f9dbb5e3 | ||
|
|
7beced1e68 | ||
|
|
c8921b77f0 | ||
|
|
482ce0a265 | ||
|
|
c570405d02 | ||
|
|
35ed465e10 | ||
|
|
747bc4c088 | ||
|
|
7c156927e4 | ||
|
|
13f0c77fb6 | ||
|
|
82d9f77eb3 | ||
|
|
151699ceb9 | ||
|
|
cc5d5bdd01 | ||
|
|
f43dadc068 | ||
|
|
b24694bea3 | ||
|
|
f4b31aa771 | ||
|
|
3cd5d00b0b | ||
|
|
7112584139 | ||
|
|
639b9c8f56 | ||
|
|
791ebc6406 | ||
|
|
059134d3d0 | ||
|
|
3b0616c150 | ||
|
|
ad5c4fdca2 | ||
|
|
b219b8fb65 | ||
|
|
73a170a388 | ||
|
|
e55e9c73b9 | ||
|
|
71ed5d3427 | ||
|
|
8b7bdfe0c7 | ||
|
|
ed6018fad1 | ||
|
|
b5f4be36d4 | ||
|
|
5c0a46b006 | ||
|
|
54ed46447a | ||
|
|
84fe3c2f40 | ||
|
|
e30329a18c | ||
|
|
22b40aab5d | ||
|
|
c3c8b1eb70 | ||
|
|
ce82ee8d0d | ||
|
|
04f0967b18 | ||
|
|
f8ef2e33de | ||
|
|
20a8ac7c98 | ||
|
|
37e9236bde | ||
|
|
fc11ff13c5 | ||
|
|
c92586ee64 | ||
|
|
38e2308d94 | ||
|
|
03044250a3 | ||
|
|
ebe7ec40e4 | ||
|
|
412626052b | ||
|
|
2a0af154c6 | ||
|
|
87debbd26b | ||
|
|
10e88fccd6 | ||
|
|
cc781c0082 | ||
|
|
5198ee997c | ||
|
|
4843d6cdc1 | ||
|
|
8ca0b2d9c3 | ||
|
|
cf589cb792 | ||
|
|
eda05e0157 | ||
|
|
4ae8a0308e | ||
|
|
88c6ce1c7e | ||
|
|
b714e2fca0 | ||
|
|
ce3a931c59 | ||
|
|
69f5d90e91 | ||
|
|
1a6ab68752 | ||
|
|
f13001e53a | ||
|
|
824b537db0 | ||
|
|
42e4bc3105 | ||
|
|
ebcedcffec | ||
|
|
b8cc84261f | ||
|
|
4e2475502a | ||
|
|
7dd9bcce2b | ||
|
|
620628dca3 | ||
|
|
5ee7d52e8c | ||
|
|
26004b1f73 | ||
|
|
59c42bc1a3 | ||
|
|
c8a7d539f3 | ||
|
|
98c86bcc0f | ||
|
|
1b138e14c3 | ||
|
|
a54e52d34b | ||
|
|
67218fb61d | ||
|
|
4eb0085243 | ||
|
|
38a8fcb69a | ||
|
|
64592d734c | ||
|
|
abddc8a864 | ||
|
|
09b45aca4f | ||
|
|
f624211620 | ||
|
|
c19cde53d6 | ||
|
|
cf8694fcd1 | ||
|
|
28d640afb4 | ||
|
|
7b432078a0 | ||
|
|
19703fed31 | ||
|
|
b01e127cc3 | ||
|
|
f0c2d81352 | ||
|
|
67896adefc | ||
|
|
d1b29b6e61 | ||
|
|
ef93aad659 | ||
|
|
616b433409 | ||
|
|
515d96eef0 | ||
|
|
85963cc58d | ||
|
|
e15611a0ea | ||
|
|
f89a2ab91d | ||
|
|
18d3fb75ac | ||
|
|
f5bc2b08f9 | ||
|
|
80e5916394 | ||
|
|
48c47f9911 | ||
|
|
c3f5849f11 | ||
|
|
c73346eda0 | ||
|
|
d976455a12 | ||
|
|
f10f80f655 |
No files matched your search
+101
-94
@@ -1,20 +1,24 @@
|
||||
FROM ubuntu:21.10
|
||||
LABEL maintainer="sgr"
|
||||
FROM ubuntu:24.04
|
||||
LABEL maintainer="wekan"
|
||||
LABEL org.opencontainers.image.ref.name="ubuntu"
|
||||
LABEL org.opencontainers.image.version="24.04"
|
||||
LABEL org.opencontainers.image.source="https://github.com/wekan/wekan"
|
||||
|
||||
# 2022-04-25:
|
||||
# - gyp does not yet work with Ubuntu 22.04 ubuntu:rolling,
|
||||
# so changing to 21.10. https://github.com/wekan/wekan/issues/4488
|
||||
|
||||
ENV BUILD_DEPS="gnupg gosu libarchive-tools wget curl bzip2 g++ build-essential python3 git ca-certificates iproute2"
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV BUILD_DEPS="apt-utils gnupg gosu wget bzip2 g++ iproute2 apt-transport-https libarchive-tools"
|
||||
ENV DEV_DEPS="curl python3 ca-certificates build-essential git"
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ENV \
|
||||
DEBUG=false \
|
||||
NODE_VERSION=v14.21.4 \
|
||||
METEOR_RELEASE=METEOR@2.13 \
|
||||
METEOR_RELEASE=METEOR@2.14 \
|
||||
USE_EDGE=false \
|
||||
METEOR_EDGE=1.5-beta.17 \
|
||||
NPM_VERSION=latest \
|
||||
NPM_VERSION=6.14.17 \
|
||||
FIBERS_VERSION=4.0.1 \
|
||||
ARCHITECTURE=linux-x64 \
|
||||
SRC_PATH=./ \
|
||||
@@ -28,15 +32,14 @@ ENV \
|
||||
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 \
|
||||
ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 \
|
||||
ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS=90 \
|
||||
RICHER_CARD_COMMENT_EDITOR=false \
|
||||
CARD_OPENED_WEBHOOK_ENABLED=false \
|
||||
ATTACHMENTS_STORE_PATH="" \
|
||||
ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM="" \
|
||||
ATTACHMENTS_UPLOAD_MIME_TYPES="" \
|
||||
ATTACHMENTS_UPLOAD_MAX_SIZE=0 \
|
||||
AVATARS_UPLOAD_EXTERNAL_PROGRAM="" \
|
||||
AVATARS_UPLOAD_MIME_TYPES="" \
|
||||
AVATARS_UPLOAD_MAX_SIZE=0 \
|
||||
RICHER_CARD_COMMENT_EDITOR=false \
|
||||
CARD_OPENED_WEBHOOK_ENABLED=false \
|
||||
MAX_IMAGE_PIXEL="" \
|
||||
IMAGE_COMPRESS_RATIO="" \
|
||||
NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE="" \
|
||||
@@ -48,10 +51,12 @@ ENV \
|
||||
MATOMO_SITE_ID="" \
|
||||
MATOMO_DO_NOT_TRACK=true \
|
||||
MATOMO_WITH_USERNAME=false \
|
||||
METRICS_ALLOWED_IP_ADDRESSES="" \
|
||||
BROWSER_POLICY_ENABLED=true \
|
||||
TRUSTED_URL="" \
|
||||
WEBHOOKS_ATTRIBUTES="" \
|
||||
OAUTH2_ENABLED=false \
|
||||
OIDC_REDIRECTION_ENABLED=false \
|
||||
OAUTH2_CA_CERT="" \
|
||||
OAUTH2_ADFS_ENABLED=false \
|
||||
OAUTH2_B2C_ENABLED=false \
|
||||
@@ -71,6 +76,9 @@ ENV \
|
||||
LDAP_ENABLE=false \
|
||||
LDAP_PORT=389 \
|
||||
LDAP_HOST="" \
|
||||
LDAP_AD_SIMPLE_AUTH="" \
|
||||
LDAP_USER_AUTHENTICATION=false \
|
||||
LDAP_USER_AUTHENTICATION_FIELD=uid \
|
||||
LDAP_BASEDN="" \
|
||||
LDAP_LOGIN_FALLBACK=false \
|
||||
LDAP_RECONNECT=true \
|
||||
@@ -88,8 +96,6 @@ ENV \
|
||||
LDAP_ENCRYPTION=false \
|
||||
LDAP_CA_CERT="" \
|
||||
LDAP_REJECT_UNAUTHORIZED=false \
|
||||
LDAP_USER_AUTHENTICATION=false \
|
||||
LDAP_USER_AUTHENTICATION_FIELD=uid \
|
||||
LDAP_USER_SEARCH_FILTER="" \
|
||||
LDAP_USER_SEARCH_SCOPE="" \
|
||||
LDAP_USER_SEARCH_FIELD="" \
|
||||
@@ -144,69 +150,32 @@ ENV \
|
||||
SAML_IDENTIFIER_FORMAT="" \
|
||||
SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE="" \
|
||||
SAML_ATTRIBUTES="" \
|
||||
DEFAULT_WAIT_SPINNER="" \
|
||||
ORACLE_OIM_ENABLED=false \
|
||||
WAIT_SPINNER="" \
|
||||
WRITABLE_PATH=/data \
|
||||
S3=""
|
||||
# \
|
||||
# NODE_OPTIONS="--max_old_space_size=4096"
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
|
||||
# Add more Node heap:
|
||||
# NODE_OPTIONS="--max_old_space_size=4096"
|
||||
# Add more stack:
|
||||
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
|
||||
|
||||
#---------------------------------------------
|
||||
# == at docker-compose.yml: AUTOLOGIN WITH OIDC/OAUTH2 ====
|
||||
# https://github.com/wekan/wekan/wiki/autologin
|
||||
#- OIDC_REDIRECTION_ENABLED=true
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
# Install OS
|
||||
RUN set -o xtrace \
|
||||
&& useradd --user-group -m --system --home-dir /home/wekan wekan \
|
||||
&& apt-get update \
|
||||
&& apt-get install --assume-yes --no-install-recommends apt-utils apt-transport-https ca-certificates 2>&1 \
|
||||
&& apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS}
|
||||
|
||||
# OLD:
|
||||
# && curl -fsSLO --compressed "https://nodejs.org/dist/$NODE_VERSION/node-$NODE_VERSION-$ARCHITECTURE.tar.xz" \
|
||||
# && curl -fsSLO --compressed "https://nodejs.org/dist/$NODE_VERSION/SHASUMS256.txt.asc" \
|
||||
|
||||
# Install NodeJS
|
||||
RUN set -o xtrace \
|
||||
&& cd /tmp \
|
||||
&& curl -fsSLO --compressed "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.xz" \
|
||||
&& curl -fsSLO --compressed "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt" \
|
||||
&& grep " node-$NODE_VERSION-$ARCHITECTURE.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
|
||||
&& tar -xJf "node-$NODE_VERSION-$ARCHITECTURE.tar.xz" -C /usr/local --strip-components=1 --no-same-owner \
|
||||
&& rm "node-$NODE_VERSION-$ARCHITECTURE.tar.xz" SHASUMS256.txt \
|
||||
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs \
|
||||
&& mkdir -p /usr/local/lib/node_modules/fibers/.node-gyp /root/.node-gyp/${NODE_VERSION} /home/wekan/.config \
|
||||
&& npm install -g npm@${NPM_VERSION} \
|
||||
&& chown wekan:wekan --recursive /home/wekan/.config
|
||||
|
||||
ENV DEBIAN_FRONTEND=dialog
|
||||
|
||||
USER wekan
|
||||
|
||||
# Install Meteor
|
||||
RUN set -o xtrace \
|
||||
&& cd /home/wekan \
|
||||
&& curl https://install.meteor.com/?release=$METEOR_VERSION --output /home/wekan/install-meteor.sh \
|
||||
# Replace tar with bsdtar in the install script; https://github.com/jshimko/meteor-launchpad/issues/39
|
||||
&& sed --in-place "s/tar -xzf.*/bsdtar -xf \"\$TARBALL_FILE\" -C \"\$INSTALL_TMPDIR\"/g" /home/wekan/install-meteor.sh \
|
||||
&& sed --in-place 's/VERBOSITY="--silent"/VERBOSITY="--progress-bar"/' /home/wekan/install-meteor.sh \
|
||||
&& printf "\n[-] Installing Meteor $METEOR_VERSION...\n\n" \
|
||||
&& sh /home/wekan/install-meteor.sh
|
||||
|
||||
ENV PATH=$PATH:/home/wekan/.meteor/
|
||||
|
||||
USER root
|
||||
|
||||
RUN echo "export PATH=$PATH" >> /etc/environment
|
||||
|
||||
USER wekan
|
||||
RUN <<EOR
|
||||
echo "export PATH=$PATH" >> /etc/environment
|
||||
EOR
|
||||
|
||||
# Copy source dir
|
||||
RUN set -o xtrace \
|
||||
&& mkdir -p /home/wekan/app/.meteor \
|
||||
&& mkdir -p /home/wekan/app/packages
|
||||
RUN <<EOR
|
||||
set -o xtrace
|
||||
|
||||
mkdir -p /home/wekan/app/.meteor
|
||||
mkdir -p /home/wekan/app/packages
|
||||
EOR
|
||||
|
||||
COPY \
|
||||
.meteor/.finished-upgraders \
|
||||
@@ -231,44 +200,83 @@ COPY \
|
||||
packages \
|
||||
/home/wekan/app/packages/
|
||||
|
||||
USER root
|
||||
# Install OS
|
||||
RUN <<EOR
|
||||
set -o xtrace
|
||||
|
||||
RUN set -o xtrace \
|
||||
&& chown -R wekan:wekan /home/wekan/app /home/wekan/.meteor
|
||||
# Add non-root user wekan
|
||||
useradd --user-group --system --home-dir /home/wekan wekan
|
||||
# OS dependencies
|
||||
apt-get update --assume-yes
|
||||
apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS} ${DEV_DEPS}
|
||||
|
||||
USER wekan
|
||||
# Meteor installer doesn't work with the default tar binary, so using bsdtar while installing.
|
||||
# https://github.com/coreos/bugs/issues/1095#issuecomment-350574389
|
||||
cp $(which tar) $(which tar)~
|
||||
ln -sf $(which bsdtar) $(which tar)
|
||||
|
||||
RUN \
|
||||
set -o xtrace && \
|
||||
# Build app
|
||||
cd /home/wekan/app && \
|
||||
/home/wekan/.meteor/meteor add standard-minifier-js && \
|
||||
/home/wekan/.meteor/meteor npm install && \
|
||||
/home/wekan/.meteor/meteor build --directory /home/wekan/app_build
|
||||
# Install NodeJS
|
||||
cd /tmp
|
||||
|
||||
RUN \
|
||||
set -o xtrace && \
|
||||
cd /home/wekan/app_build/bundle/programs/server/ && \
|
||||
chmod u+w package.json npm-shrinkwrap.json && \
|
||||
npm install && \
|
||||
cd node_modules/fibers && \
|
||||
node build.js
|
||||
# Download nodejs
|
||||
wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz"
|
||||
wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt"
|
||||
|
||||
# Verify nodejs authenticity
|
||||
grep "node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz" "SHASUMS256.txt" | shasum -a 256 -c -
|
||||
rm -f "SHASUMS256.txt"
|
||||
|
||||
# Install Node
|
||||
tar xzf "node-$NODE_VERSION-$ARCHITECTURE.tar.gz" -C /usr/local --strip-components=1 --no-same-owner
|
||||
rm "node-$NODE_VERSION-$ARCHITECTURE.tar.gz" "SHASUMS256.txt"
|
||||
ln -s "/usr/local/bin/node" "/usr/local/bin/nodejs"
|
||||
mkdir -p "/opt/nodejs/lib/node_modules/fibers/.node-gyp" "/root/.node-gyp/${NODE_VERSION} /home/wekan/.config"
|
||||
|
||||
# Install node dependencies
|
||||
npm install -g npm@${NPM_VERSION}
|
||||
chown --recursive wekan:wekan /home/wekan/.config
|
||||
|
||||
# Install Meteor
|
||||
cd /home/wekan
|
||||
chown --recursive wekan:wekan /home/wekan
|
||||
echo "Starting meteor ${METEOR_RELEASE} installation... \n"
|
||||
gosu wekan:wekan curl https://install.meteor.com/ | /bin/sh
|
||||
mv /root/.meteor /home/wekan/
|
||||
chown --recursive wekan:wekan /home/wekan/.meteor
|
||||
|
||||
# sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' /home/wekan/app/packages/meteor-useraccounts-core/package.js
|
||||
cd /home/wekan/.meteor
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor -- help
|
||||
|
||||
# Build app (Development)
|
||||
cd /home/wekan/app
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor add standard-minifier-js
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor npm install
|
||||
|
||||
# Put back the original tar
|
||||
mv $(which tar)~ $(which tar)
|
||||
|
||||
USER root
|
||||
# Cleanup
|
||||
RUN \
|
||||
set -o xtrace && \
|
||||
apt-get clean -y && \
|
||||
apt-get autoremove -y && \
|
||||
rm -Rf /tmp/* && \
|
||||
rm -Rf /home/wekan/app_build && \
|
||||
rm -Rf /var/cache/apt /var/lib/apt/lists && \
|
||||
rm -Rf /var/lib/apt/lists/*
|
||||
apt-get remove --purge --assume-yes ${BUILD_DEPS}
|
||||
apt-get install --assume-yes --no-install-recommends build-essential
|
||||
apt-get autoremove --assume-yes
|
||||
apt-get clean --assume-yes
|
||||
rm -Rf /tmp/*
|
||||
rm -Rf /var/lib/apt/lists/*
|
||||
rm -Rf /var/cache/apt
|
||||
rm -Rf /var/lib/apt/lists
|
||||
rm -Rf /home/wekan/app_build
|
||||
|
||||
mkdir /data
|
||||
chown wekan --recursive /data
|
||||
EOR
|
||||
|
||||
USER wekan
|
||||
|
||||
ENV PORT=3000
|
||||
EXPOSE $PORT
|
||||
|
||||
STOPSIGNAL SIGKILL
|
||||
WORKDIR /home/wekan/app
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
@@ -278,7 +286,6 @@ WORKDIR /home/wekan/app
|
||||
# Add more stack:
|
||||
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
|
||||
#---------------------------------------------------------------------
|
||||
#TODO:
|
||||
#CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 /build/main.js"]
|
||||
#
|
||||
|
||||
CMD ["/home/wekan/.meteor/meteor", "run", "--verbose", "--settings", "settings.json"]
|
||||
+31
-11
@@ -1,18 +1,26 @@
|
||||
## Issue
|
||||
<!--
|
||||
Please report these elsewhere:
|
||||
|
||||
Please report these issues elsewhere:
|
||||
|
||||
- SECURITY ISSUES, PGP EMAIL: https://github.com/wekan/wekan/blob/main/SECURITY.md
|
||||
- UCS: https://github.com/wekan/univention/issues
|
||||
|
||||
**[PLEASE UPGRADE](https://github.com/wekan/wekan/wiki/Backup)** to the newest WeKan ® before reporting an issue.
|
||||
If WeKan Snap is slow, try this: https://github.com/wekan/wekan/wiki/Cron
|
||||
|
||||
**[PLEASE UPGRADE](https://github.com/wekan/wekan/wiki/Backup)** to the newest
|
||||
WeKan ® before reporting an issue, if possible.
|
||||
|
||||
Please search existing Open and Closed issues, most questions have already been answered.
|
||||
|
||||
If you can not login for any reason: https://github.com/wekan/wekan/wiki/Forgot-Password
|
||||
Email settings, only SMTP MAIL_URL and MAIL_FROM are in use: https://github.com/wekan/wekan/wiki/Troubleshooting-Mail
|
||||
-->
|
||||
Email settings, only SMTP MAIL_URL and MAIL_FROM are in use:
|
||||
https://github.com/wekan/wekan/wiki/Troubleshooting-Mail
|
||||
|
||||
### Server Setup Information
|
||||
<!-- Please anonymize info, and do not any of your Wekan board URLs, passwords, API tokens etc to this public issue. -->
|
||||
|
||||
Please anonymize info, and do not any of your Wekan board URLs, passwords,
|
||||
API tokens etc to this public issue.
|
||||
|
||||
* Did you test in newest Wekan?:
|
||||
* Did you configure root-url correctly so Wekan cards open correctly (see https://github.com/wekan/wekan/wiki/Settings)?
|
||||
* Operating System:
|
||||
@@ -23,13 +31,25 @@ Email settings, only SMTP MAIL_URL and MAIL_FROM are in use: https://github.com/
|
||||
* What webbrowser version are you using (Wekan should work on all modern browsers that support Javascript)?
|
||||
|
||||
### Problem description
|
||||
<!-- Add a recorded animated gif (e.g. with https://github.com/phw/peek) about how it works currently, and screenshot mockups how it should work. -->
|
||||
|
||||
Add a recorded animated gif (e.g. with https://github.com/phw/peek) about
|
||||
how it works currently, and screenshot mockups how it should work.
|
||||
|
||||
|
||||
#### Reproduction Steps
|
||||
|
||||
#### Logs
|
||||
<!-- Check Right Click>Inspect>Console in you browser - generally Chrome shows more detailed info than Firefox. -->
|
||||
|
||||
<!-- Please anonymize logs.
|
||||
|
||||
#### Logs
|
||||
|
||||
Check Right Click / Inspect / Console in you browser - generally Chromium
|
||||
based browsers show more detailed info than Firefox based browsers.
|
||||
|
||||
Please anonymize logs.
|
||||
|
||||
Snap: sudo snap logs wekan.wekan
|
||||
|
||||
Docker: sudo docker logs wekan-app
|
||||
If logs are very long, attach them in .zip file -->
|
||||
|
||||
If logs are very long, attach them in .zip file
|
||||
|
||||
@@ -11,4 +11,4 @@ jobs:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v3
|
||||
uses: actions/dependency-review-action@v4
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
# https://github.com/docker/login-action
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -48,14 +48,14 @@ jobs:
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@9dc751fe249ad99385a2583ee0d084c400eee04e
|
||||
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
# Build and push Docker image with Buildx (don't push on PR)
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56
|
||||
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
# CACHING
|
||||
- name: Install Meteor
|
||||
id: cache-meteor-install
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.meteor
|
||||
key: v1-meteor-${{ hashFiles('.meteor/versions') }}
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
|
||||
- name: Cache NPM dependencies
|
||||
id: cache-meteor-npm
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: v1-npm-${{ hashFiles('package-lock.json') }}
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
|
||||
- name: Cache Meteor build
|
||||
id: cache-meteor-build
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
.meteor/local/resolver-result-cache.json
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
v1-meteor_build_cache-
|
||||
|
||||
- name: Setup meteor
|
||||
uses: meteorengineer/setup-meteor@v1
|
||||
uses: meteorengineer/setup-meteor@v2
|
||||
with:
|
||||
meteor-release: '2.2'
|
||||
|
||||
@@ -157,7 +157,7 @@ jobs:
|
||||
|
||||
|
||||
- name: Coverage Report
|
||||
uses: VeryGoodOpenSource/very_good_coverage@v2.2.0
|
||||
uses: VeryGoodOpenSource/very_good_coverage@v3.0.0
|
||||
with:
|
||||
path: ".coverage/lcov.info"
|
||||
min_coverage: 1 # TODO add tests and increase to 95!
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
accounts-base@2.2.9
|
||||
accounts-base@2.2.10
|
||||
accounts-oauth@1.4.3
|
||||
accounts-password@2.4.0
|
||||
aldeed:collection2@2.10.0
|
||||
@@ -63,7 +63,7 @@ localstorage@1.2.0
|
||||
logging@1.3.3
|
||||
matb33:collection-hooks@1.3.0
|
||||
mdg:validation-error@0.5.1
|
||||
meteor@1.11.4
|
||||
meteor@1.11.5
|
||||
meteor-autosize@5.0.1
|
||||
meteor-base@1.5.1
|
||||
meteorhacks:aggregate@1.3.0
|
||||
@@ -123,7 +123,7 @@ reactive-var@1.0.12
|
||||
reload@1.3.1
|
||||
retry@1.1.0
|
||||
routepolicy@1.1.1
|
||||
service-configuration@1.3.2
|
||||
service-configuration@1.3.3
|
||||
session@1.2.1
|
||||
sha@1.0.9
|
||||
shell-server@0.5.0
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = es_AR: es-AR, es_419: es-LA, es_TX: es-TX, he_IL: he-IL, zh_CN: zh-CN, ar_EG: ar-EG, cs_CZ: cs-CZ, fa_IR: fa-IR, ms_MY: ms-MY, nl_NL: nl-NL, de_CH: de-CH, en_IT: en-IT, uz_UZ: uz-UZ, fr_CH: fr-CH, hi_IN: hi-IN, et_EE: et-EE, es_PE: es-PE, es_MX: es-MX, gl_ES: gl-ES, mn_MN: mn, sl_SI: sl, zh_TW: zh-TW, ast_ES: ast-ES, es_CL: es-CL, ja_JP: ja, lv_LV: lv, ro_RO: ro-RO, az_AZ: az-AZ, cy_GB: cy-GB, gu_IN: gu-IN, pl_PL: pl-PL, vep: ve-PP, en_BR: en-BR, en@ysv: en-YS, hu_HU: hu, ko_KR: ko-KR, pt_BR: pt-BR, zh_HK: zh-HK, zu_ZA: zu-ZA, en_MY: en-MY, ja-Hira: ja-HI, fi_FI: fi, vec: ve-CC, vi_VN: vi-VN, fr_FR: fr-FR, id_ID: id, zh_Hans: zh-Hans, en_DE: en-DE, en_GB: en-GB, el_GR: el-GR, uk_UA: uk-UA, az@latin: az-LA, de_AT: de-AT, uz@Latn: uz-LA, vls: vl-SS, ar_DZ: ar-DZ, bg_BG: bg, es_PY: es-PY, fy_NL: fy-NL, uz@Arab: uz-AR, ru_UA: ru-UA, war: wa-RR, zh_CN.GB2312: zh-GB
|
||||
lang_map = te_IN: te-IN, es_AR: es-AR, es_419: es-LA, es_TX: es-TX, he_IL: he-IL, zh_CN: zh-CN, ar_EG: ar-EG, cs_CZ: cs-CZ, fa_IR: fa-IR, ms_MY: ms-MY, nl_NL: nl-NL, de_CH: de-CH, en_IT: en-IT, uz_UZ: uz-UZ, fr_CH: fr-CH, hi_IN: hi-IN, et_EE: et-EE, es_PE: es-PE, es_MX: es-MX, gl_ES: gl-ES, mn_MN: mn, sl_SI: sl, zh_TW: zh-TW, ast_ES: ast-ES, es_CL: es-CL, ja_JP: ja, lv_LV: lv, ro_RO: ro-RO, az_AZ: az-AZ, cy_GB: cy-GB, gu_IN: gu-IN, pl_PL: pl-PL, vep: ve-PP, en_BR: en-BR, en@ysv: en-YS, hu_HU: hu, ko_KR: ko-KR, pt_BR: pt-BR, zh_HK: zh-HK, zu_ZA: zu-ZA, en_MY: en-MY, ja-Hira: ja-HI, fi_FI: fi, vec: ve-CC, vi_VN: vi-VN, fr_FR: fr-FR, id_ID: id, zh_Hans: zh-Hans, en_DE: en-DE, en_GB: en-GB, el_GR: el-GR, uk_UA: uk-UA, az@latin: az-LA, de_AT: de-AT, uz@Latn: uz-LA, vls: vl-SS, ar_DZ: ar-DZ, bg_BG: bg, es_PY: es-PY, fy_NL: fy-NL, uz@Arab: uz-AR, ru_UA: ru-UA, war: wa-RR, zh_CN.GB2312: zh-GB
|
||||
|
||||
[o:wekan:p:wekan:r:application]
|
||||
file_filter = imports/i18n/data/<lang>.i18n.json
|
||||
|
||||
Vendored
+48
-36
@@ -1,45 +1,57 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Meteor: Chrome",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Meteor: Node",
|
||||
"runtimeExecutable": "meteor",
|
||||
"runtimeArgs": [
|
||||
"--port=4000",
|
||||
"--exclude-archs=web.browser.legacy,web.cordova",
|
||||
"--raw-logs"
|
||||
],
|
||||
"env": {
|
||||
"WRITABLE_PATH": "/tmp/uploads",
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Meteor: Node",
|
||||
"runtimeExecutable": "/home/wekan/.meteor/meteor",
|
||||
"runtimeArgs": ["run", "--inspect-brk=9229"],
|
||||
"outputCapture": "std",
|
||||
"port": 9229,
|
||||
"timeout": 60000
|
||||
"outputCapture": "std",
|
||||
"restart": true,
|
||||
"timeout": 60000
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Meteor: Chrome",
|
||||
"url": "http://localhost:4000",
|
||||
"sourceMapPathOverrides": {
|
||||
"meteor://💻app/*": "${workspaceFolder}/*"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Test: Node",
|
||||
"runtimeExecutable": "meteor",
|
||||
"runtimeArgs": [
|
||||
"test",
|
||||
"--inspect-brk=9229",
|
||||
"--port=4040",
|
||||
"--exclude-archs=web.browser.legacy,web.cordova",
|
||||
"--driver-package=meteortesting:mocha",
|
||||
"--settings=settings.json"
|
||||
],
|
||||
"outputCapture": "std",
|
||||
"port": 9229,
|
||||
"timeout": 60000
|
||||
}
|
||||
"userDataDir": "${env:HOME}/.vscode/chrome"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Test: Node",
|
||||
"runtimeExecutable": "meteor",
|
||||
"runtimeArgs": [
|
||||
"test",
|
||||
"--port=4040",
|
||||
"--exclude-archs=web.browser.legacy,web.cordova",
|
||||
"--driver-package=meteortesting:mocha",
|
||||
"--settings=settings.json",
|
||||
"--raw-logs"
|
||||
],
|
||||
"env": {
|
||||
"TEST_WATCH": "1"
|
||||
},
|
||||
"outputCapture": "std",
|
||||
"timeout": 60000
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Meteor: All",
|
||||
"configurations": ["Meteor: Node", "Meteor: Chrome"]
|
||||
}
|
||||
{
|
||||
"name": "Meteor: All",
|
||||
"configurations": ["Meteor: Node", "Meteor: Chrome"]
|
||||
}
|
||||
]
|
||||
}
|
||||
+995
-1
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -11,7 +11,7 @@ For all code at WeKan GitHub Organization https://github.com/wekan
|
||||
|
||||
## Private reports
|
||||
|
||||
- Email support (at) wekan.team using [this PGP public key](support-at-wekan.team_pgp-publickey.asc) if possible
|
||||
- Email support@wekan.team
|
||||
- Security issues: [SECURITY.md](SECURITY.md)
|
||||
- License violations
|
||||
- Anything private, sensitive or negative
|
||||
|
||||
+3
-7
@@ -27,13 +27,9 @@ CVE Hall of Fame is at https://wekan.github.io/hall-of-fame/
|
||||
|
||||
## Contributing to Documentation Wiki
|
||||
|
||||
Please clone wiki:
|
||||
```
|
||||
git clone https://github.com/wekan/wekan.wiki
|
||||
```
|
||||
Edit .md files, and add changed files in .zip attachment
|
||||
directly to comment of new issue at
|
||||
https://github.com/wekan/wekan/issues
|
||||
Fork WeKan repo https://github.com/wekan/wekan ,
|
||||
edit `docs` directory content at GitHub web interface,
|
||||
and click send PR.
|
||||
|
||||
## Contributing code
|
||||
|
||||
|
||||
+95
-141
@@ -1,38 +1,76 @@
|
||||
FROM --platform=linux/amd64 ubuntu:23.10 as wekan
|
||||
LABEL maintainer="wekan" \
|
||||
org.opencontainers.image.ref.name="ubuntu" \
|
||||
org.opencontainers.image.version="23.10" \
|
||||
org.opencontainers.image.source="https://github.com/wekan/wekan"
|
||||
FROM scratch AS downloads
|
||||
|
||||
# 2022-09-04:
|
||||
# - above "--platform=linux/amd64 ubuntu:22.04 as wekan" is needed to build Dockerfile
|
||||
# correctly on Mac M1 etc, to not get this error:
|
||||
# https://stackoverflow.com/questions/71040681/qemu-x86-64-could-not-open-lib64-ld-linux-x86-64-so-2-no-such-file-or-direc
|
||||
# Download NodeJS bundle
|
||||
ADD https://static.meteor.com/dev-bundle-node-os/v14.21.4/node-v14.21.4-linux-x64.tar.gz node.tar.gz
|
||||
|
||||
# 2022-04-25:
|
||||
# - gyp does not yet work with Ubuntu 22.04 ubuntu:rolling,
|
||||
# so changing to 21.10. https://github.com/wekan/wekan/issues/4488
|
||||
# Download Meteor bundle
|
||||
ADD https://static.meteor.com/packages-bootstrap/2.16/meteor-bootstrap-os.linux.x86_64.tar.gz meteor.tar.gz
|
||||
|
||||
# 2021-09-18:
|
||||
# - Above Ubuntu base image copied from Docker Hub ubuntu:hirsute-20210825
|
||||
# to Quay to avoid Docker Hub rate limits.
|
||||
FROM ubuntu:24.04 AS build
|
||||
|
||||
# Set the environment variables (defaults where required)
|
||||
# DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303
|
||||
# ENV BUILD_DEPS="paxctl"
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
# Install node from official archive
|
||||
RUN --mount=type=bind,from=downloads,source=node.tar.gz,target=node.tar.gz \
|
||||
tar xzf node.tar.gz --strip-components=1 --keep-old-files --no-same-owner
|
||||
|
||||
ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-essential git ca-certificates python3" \
|
||||
DEBUG=false \
|
||||
NODE_VERSION=v14.21.4 \
|
||||
METEOR_RELEASE=METEOR@2.13.3 \
|
||||
USE_EDGE=false \
|
||||
METEOR_EDGE=1.5-beta.17 \
|
||||
NPM_VERSION=9.8.1 \
|
||||
FIBERS_VERSION=4.0.1 \
|
||||
ARCHITECTURE=linux-x64 \
|
||||
SRC_PATH=./ \
|
||||
WITH_API=true \
|
||||
# Install meteor from official archive
|
||||
RUN --mount=type=bind,from=downloads,source=meteor.tar.gz,target=meteor.tar.gz \
|
||||
tar xzf meteor.tar.gz -C $HOME --no-same-owner
|
||||
|
||||
# Update the npm version
|
||||
RUN npm install -g npm@6.14.17
|
||||
|
||||
# Install several build dependencies
|
||||
RUN apt update && \
|
||||
apt install --yes --no-install-suggests --no-install-recommends ssh git g++ curl ca-certificates && \
|
||||
rm -r /var/lib/apt/lists
|
||||
|
||||
# Change to temporary working directory
|
||||
WORKDIR /workdir
|
||||
|
||||
# Copy package.json and package-lock.json to install application dependencies
|
||||
COPY package.json .
|
||||
COPY package-lock.json .
|
||||
|
||||
# Install build dependencies
|
||||
RUN $HOME/.meteor/meteor npm install --production
|
||||
|
||||
# Copy meteor application configurations
|
||||
COPY .meteor .meteor
|
||||
|
||||
# Copy application sources
|
||||
COPY packages packages
|
||||
COPY imports imports
|
||||
COPY config config
|
||||
COPY models models
|
||||
COPY public public
|
||||
COPY server server
|
||||
COPY client client
|
||||
|
||||
# Build the application
|
||||
RUN $HOME/.meteor/meteor build --directory /build --allow-superuser
|
||||
|
||||
# Enter server bundle directory
|
||||
WORKDIR /build/bundle/programs/server
|
||||
|
||||
# Install server dependencies
|
||||
RUN $HOME/.meteor/meteor npm install --production
|
||||
|
||||
# Enter installation directory of fibers
|
||||
WORKDIR /build/bundle/programs/server/node_modules/fibers
|
||||
|
||||
# Build fibers integrations
|
||||
RUN node build.js
|
||||
|
||||
FROM ubuntu:24.04 AS wekan
|
||||
|
||||
# Add metadata to image
|
||||
LABEL maintainer="wekan"
|
||||
LABEL org.opencontainers.image.ref.name="ubuntu"
|
||||
LABEL org.opencontainers.image.version="24.04"
|
||||
LABEL org.opencontainers.image.source="https://github.com/wekan/wekan"
|
||||
|
||||
# Set default environment variables
|
||||
ENV WITH_API=true \
|
||||
RESULTS_PER_PAGE="" \
|
||||
DEFAULT_BOARD_ID="" \
|
||||
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \
|
||||
@@ -165,120 +203,36 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build-
|
||||
WRITABLE_PATH=/data \
|
||||
S3=""
|
||||
|
||||
# NODE_OPTIONS="--max_old_space_size=4096" \
|
||||
|
||||
#---------------------------------------------
|
||||
# == at docker-compose.yml: AUTOLOGIN WITH OIDC/OAUTH2 ====
|
||||
# https://github.com/wekan/wekan/wiki/autologin
|
||||
#- OIDC_REDIRECTION_ENABLED=true
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
# Copy the app to the image
|
||||
COPY ${SRC_PATH} /home/wekan/app
|
||||
|
||||
RUN \
|
||||
set -o xtrace && \
|
||||
# Add non-root user wekan
|
||||
useradd --user-group --system --home-dir /home/wekan wekan && \
|
||||
\
|
||||
# OS dependencies
|
||||
apt-get update -y && apt-get install -y --no-install-recommends ${BUILD_DEPS} && \
|
||||
\
|
||||
# Meteor installer doesn't work with the default tar binary, so using bsdtar while installing.
|
||||
# https://github.com/coreos/bugs/issues/1095#issuecomment-350574389
|
||||
cp $(which tar) $(which tar)~ && \
|
||||
ln -sf $(which bsdtar) $(which tar) && \
|
||||
\
|
||||
# Download nodejs
|
||||
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
|
||||
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt && \
|
||||
#wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
|
||||
#wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
|
||||
#---------------------------------------------------------------------------------------------
|
||||
\
|
||||
# Verify nodejs authenticity
|
||||
grep node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz SHASUMS256.txt | shasum -a 256 -c - && \
|
||||
rm -f SHASUMS256.txt && \
|
||||
#grep ${NODE_VERSION}-${ARCHITECTURE}.tar.gz SHASUMS256.txt.asc | shasum -a 256 -c - && \
|
||||
#rm -f SHASUMS256.txt.asc && \
|
||||
\
|
||||
# Install Node
|
||||
tar xvzf node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
|
||||
rm node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz && \
|
||||
mv node-${NODE_VERSION}-${ARCHITECTURE} /opt/nodejs && \
|
||||
ln -s /opt/nodejs/bin/node /usr/bin/node && \
|
||||
ln -s /opt/nodejs/bin/npm /usr/bin/npm && \
|
||||
mkdir -p /opt/nodejs/lib/node_modules/fibers/.node-gyp /root/.node-gyp/${NODE_VERSION} /home/wekan/.config && \
|
||||
chown wekan --recursive /home/wekan/.config && \
|
||||
\
|
||||
#DOES NOT WORK: paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303
|
||||
#paxctl -mC `which node` && \
|
||||
\
|
||||
# Install Node dependencies. Python path for node-gyp.
|
||||
#npm install -g npm@${NPM_VERSION} && \
|
||||
\
|
||||
# Change user to wekan and install meteor
|
||||
cd /home/wekan/ && \
|
||||
chown wekan --recursive /home/wekan && \
|
||||
echo "Starting meteor ${METEOR_RELEASE} installation... \n" && \
|
||||
gosu wekan:wekan curl https://install.meteor.com/ | /bin/sh && \
|
||||
mv /root/.meteor /home/wekan/ && \
|
||||
chown wekan --recursive /home/wekan/.meteor && \
|
||||
\
|
||||
sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' /home/wekan/app/packages/meteor-useraccounts-core/package.js && \
|
||||
cd /home/wekan/.meteor && \
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor -- help; \
|
||||
\
|
||||
# Build app
|
||||
cd /home/wekan/app && \
|
||||
mkdir -p /home/wekan/.npm && \
|
||||
chown wekan --recursive /home/wekan/.npm /home/wekan/.config /home/wekan/.meteor && \
|
||||
chmod u+w *.json && \
|
||||
gosu wekan:wekan meteor npm install && \
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor build --directory /home/wekan/app_build && \
|
||||
cd /home/wekan/app_build/bundle/programs/server/ && \
|
||||
chmod u+w *.json && \
|
||||
gosu wekan:wekan meteor npm install && \
|
||||
cd node_modules/fibers && \
|
||||
node build.js && \
|
||||
cd ../.. && \
|
||||
# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc.
|
||||
rm -rf /home/wekan/app_build/bundle/programs/web.browser.legacy && \
|
||||
mv /home/wekan/app_build/bundle /build && \
|
||||
\
|
||||
# Put back the original tar
|
||||
mv $(which tar)~ $(which tar) && \
|
||||
\
|
||||
# Cleanup
|
||||
apt-get remove --purge -y ${BUILD_DEPS} && \
|
||||
apt-get autoremove -y && \
|
||||
npm uninstall -g api2html &&\
|
||||
rm -R /tmp/* && \
|
||||
rm -R /var/lib/apt/lists/* && \
|
||||
rm -R /home/wekan/.meteor && \
|
||||
rm -R /home/wekan/app && \
|
||||
rm -R /home/wekan/app_build && \
|
||||
# Install node and application sources from build stage
|
||||
RUN --mount=type=bind,from=downloads,source=node.tar.gz,target=node.tar.gz \
|
||||
--mount=type=bind,from=build,source=/build,target=/build \
|
||||
# Install NodeJS
|
||||
tar xzf node.tar.gz --strip-components=1 --keep-old-files --no-same-owner && \
|
||||
# Add the wekan user
|
||||
useradd --uid 999 --user-group --system wekan && \
|
||||
# Copy wekan bundle
|
||||
cp -r /build/bundle /wekan && \
|
||||
# Remove the web.browser.legacy platform
|
||||
rm -r /wekan/programs/web.browser.legacy && \
|
||||
# Change ownership of wekan directory
|
||||
chown wekan:wekan -R /wekan && \
|
||||
# Create the data directory
|
||||
mkdir /data && \
|
||||
chown wekan --recursive /data
|
||||
#cat /home/wekan/python/esprima-python/files.txt | xargs rm -R && \
|
||||
#rm -R /home/wekan/python
|
||||
#rm /home/wekan/install_meteor.sh
|
||||
# Change ownership of data directory
|
||||
chown wekan:root -R /data
|
||||
|
||||
ENV PORT=8080
|
||||
EXPOSE $PORT
|
||||
# Change to the wekan user
|
||||
USER wekan
|
||||
|
||||
STOPSIGNAL SIGKILL
|
||||
# Set the port to listen on
|
||||
ENV PORT=8080
|
||||
EXPOSE ${PORT}
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
|
||||
# Add more Node heap:
|
||||
# NODE_OPTIONS="--max_old_space_size=4096"
|
||||
# Add more stack:
|
||||
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
|
||||
#---------------------------------------------------------------------
|
||||
#
|
||||
# CMD ["node", "/build/main.js"]
|
||||
# Define the stop signal for node
|
||||
STOPSIGNAL SIGINT
|
||||
|
||||
#CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 /build/main.js"]
|
||||
CMD ["bash", "-c", "ulimit -s 65500; exec node /build/main.js"]
|
||||
# Declare a volume on /data
|
||||
VOLUME /data
|
||||
|
||||
# Set startup command
|
||||
CMD ["node", "/wekan/main.js"]
|
||||
@@ -88,5 +88,6 @@ RUN \
|
||||
EXPOSE $PORT
|
||||
USER wekan
|
||||
|
||||
# CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 --max-old-space-size=8192 /home/wekan/bundle/main.js"]
|
||||
CMD ["bash", "-c", "ulimit -s 65500; exec node /home/wekan/bundle/main.js"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2023 The Wekan Team
|
||||
Copyright (c) 2014-2024 The Wekan Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -12,7 +12,7 @@ https://wekan.github.io / Install WeKan ® Server
|
||||
- [Quay](https://quay.io/repository/wekan/wekan)
|
||||
- [Docker Hub](https://hub.docker.com/r/wekanteam/wekan)
|
||||
|
||||
docker-compose.yml at https://github.com/wekan/wekan
|
||||
docker-compose.yml at https://github.com/wekan/wekan/blob/main/docker-compose.yml
|
||||
|
||||
## Standards
|
||||
|
||||
@@ -29,7 +29,7 @@ docker-compose.yml at https://github.com/wekan/wekan
|
||||
|
||||
## [Translate WeKan ® at Transifex](https://app.transifex.com/wekan/)
|
||||
|
||||
Translations to non-English languages are accepted only at [Transifex](https://app.transifex.com/wekan/) using webbrowser.
|
||||
Translations to non-English languages are accepted only at [Transifex](https://app.transifex.com/wekan/wekan) using webbrowser.
|
||||
New English strings of new features can be added as PRs to master branch file wekan/imports/i18n/data/en.i18n.json .
|
||||
|
||||
## [WeKan ® feature requests and bugs](https://github.com/wekan/wekan/issues)
|
||||
|
||||
+2
-3
@@ -1,8 +1,7 @@
|
||||
About money, see [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||
|
||||
Security is very important to us. If you discover any issue regarding security, please disclose
|
||||
the information responsibly by sending an email to support (at) wekan.team using
|
||||
[this PGP public key](support-at-wekan.team_pgp-publickey.asc) and not by
|
||||
the information responsibly by sending an email to security@wekan.team and not by
|
||||
creating a GitHub issue. We will respond swiftly to fix verifiable security issues.
|
||||
|
||||
We thank you with a place at our hall of fame page, that is
|
||||
@@ -218,7 +217,7 @@ Typical already known or "no impact" bugs such as:
|
||||
- Email spoofing, SPF, DMARC & DKIM. Wekan does not include email server.
|
||||
|
||||
Wekan is Open Source with MIT license, and free to use also for commercial use.
|
||||
We welcome all fixes to improve security by email to security (at) wekan.team .
|
||||
We welcome all fixes to improve security by email to security@wekan.team
|
||||
|
||||
## Bonus Points
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
||||
appVersion: "v7.26.0"
|
||||
appVersion: "v7.78.0"
|
||||
files:
|
||||
userUploads:
|
||||
- README.md
|
||||
|
||||
@@ -37,9 +37,24 @@ If *nix: chmod +x api.py => ./api.py users
|
||||
python3 api.py customfields BOARDID # Custom Fields of BOARDID
|
||||
python3 api.py customfield BOARDID CUSTOMFIELDID # Info of CUSTOMFIELDID
|
||||
python3 api.py addcustomfieldtoboard AUTHORID BOARDID NAME TYPE SETTINGS SHOWONCARD AUTOMATICALLYONCARD SHOWLABELONMINICARD SHOWSUMATTOPOFLIST # Add Custom Field to Board
|
||||
python3 api.py editcustomfield BOARDID LISTID CARDID CUSTOMFIELDID NEWCUSTOMFIELDVALUE
|
||||
python3 api.py editcustomfield BOARDID LISTID CARDID CUSTOMFIELDID NEWCUSTOMFIELDVALUE # Edit Custom Field
|
||||
python3 api.py listattachments BOARDID # List attachments
|
||||
python3 api.py cardsbyswimlane SWIMLANEID LISTID # Retrieve cards list on a swimlane
|
||||
python3 api.py getcard BOARDID LISTID CARDID # Get card info
|
||||
python3 api.py addlabel BOARDID LISTID CARDID LABELID # Add label to a card
|
||||
python3 api.py addcardwithlabel AUTHORID BOARDID SWIMLANEID LISTID CARDTITLE CARDDESCRIPTION LABELIDS # Add a card and a label
|
||||
python3 api.py editboardtitle BOARDID NEWBOARDTITLE # Edit board title
|
||||
python3 api.py copyboard BOARDID NEWBOARDTITLE # Copy a board
|
||||
python3 api.py createlabel BOARDID LABELCOLOR LABELNAME (Color available: `white`, `green`, `yellow`, `orange`, `red`, `purple`, `blue`, `sky`, `lime`, `pink`, `black`, `silver`, `peachpuff`, `crimson`, `plum`, `darkgreen`, `slateblue`, `magenta`, `gold`, `navy`, `gray`, `saddlebrown`, `paleturquoise`, `mistyrose`, `indigo`) # Create a new label
|
||||
python3 api.py editcardcolor BOARDID LISTID CARDID COLOR (Color available: `white`, `green`, `yellow`, `orange`, `red`, `purple`, `blue`, `sky`, `lime`, `pink`, `black`, `silver`, `peachpuff`, `crimson`, `plum`, `darkgreen`, `slateblue`, `magenta`, `gold`, `navy`, `gray`, `saddlebrown`, `paleturquoise`, `mistyrose`, `indigo`) # Edit card color
|
||||
python3 api.py addchecklist BOARDID CARDID TITLE ITEM1 ITEM2 ITEM3 ITEM4 (You can add multiple items or just one, or also without any item, just TITLE works as well. * If items or Title contains spaces, you should add ' between them.) # Add checklist + item on a card
|
||||
python3 api.py deleteallcards BOARDID SWIMLANEID ( * Be careful will delete ALL CARDS INSIDE the swimlanes automatically in every list * ) # Delete all cards on a swimlane
|
||||
python3 api.py checklistid BOARDID CARDID # Retrieve Checklist ID attached to a card
|
||||
python3 api.py checklistinfo BOARDID CARDID CHECKLISTID # Get checklist info
|
||||
python3 api.py get_list_cards_count BOARDID LISTID # Retrieve how many cards in a list
|
||||
python3 api.py get_board_cards_count BOARDID # Retrieve how many cards in a board
|
||||
|
||||
|
||||
Admin API:
|
||||
python3 api.py users # All users
|
||||
python3 api.py boards # All Public Boards
|
||||
@@ -179,6 +194,52 @@ if arguments == 10:
|
||||
print(body.text)
|
||||
# ------- ADD CUSTOM FIELD TO BOARD END -----------
|
||||
|
||||
if arguments == 8:
|
||||
|
||||
if sys.argv[1] == 'addcardwithlabel':
|
||||
# ------- ADD CARD WITH LABEL START -----------
|
||||
authorid = sys.argv[2]
|
||||
boardid = sys.argv[3]
|
||||
swimlaneid = sys.argv[4]
|
||||
listid = sys.argv[5]
|
||||
cardtitle = sys.argv[6]
|
||||
carddescription = sys.argv[7]
|
||||
labelIds = sys.argv[8] # Aggiunto labelIds
|
||||
|
||||
cardtolist = wekanurl + apiboards + boardid + s + l + s + listid + s + cs
|
||||
# Add card
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
post_data = {
|
||||
'authorId': '{}'.format(authorid),
|
||||
'title': '{}'.format(cardtitle),
|
||||
'description': '{}'.format(carddescription),
|
||||
'swimlaneId': '{}'.format(swimlaneid),
|
||||
'labelIds': labelIds
|
||||
}
|
||||
|
||||
body = requests.post(cardtolist, data=post_data, headers=headers)
|
||||
print(body.text)
|
||||
|
||||
# If ok id card
|
||||
if body.status_code == 200:
|
||||
card_data = body.json()
|
||||
new_card_id = card_data.get('_id')
|
||||
|
||||
# Updating card
|
||||
if new_card_id:
|
||||
edcard = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + s + new_card_id
|
||||
put_data = {'labelIds': labelIds}
|
||||
body = requests.put(edcard, data=put_data, headers=headers)
|
||||
print("=== EDIT CARD ===\n")
|
||||
body = requests.get(edcard, headers=headers)
|
||||
data2 = body.text.replace('}', "}\n")
|
||||
print(data2)
|
||||
else:
|
||||
print("Error obraining ID.")
|
||||
else:
|
||||
print("Error adding card.")
|
||||
# ------- ADD CARD WITH LABEL END -----------
|
||||
|
||||
if arguments == 7:
|
||||
|
||||
if sys.argv[1] == 'addcard':
|
||||
@@ -237,7 +298,53 @@ if arguments == 6:
|
||||
print(data2)
|
||||
# ------- EDIT CUSTOMFIELD END -----------
|
||||
|
||||
if arguments == 4:
|
||||
if arguments == 5:
|
||||
|
||||
if sys.argv[1] == 'addlabel':
|
||||
|
||||
# ------- EDIT CARD ADD LABEL START -----------
|
||||
boardid = sys.argv[2]
|
||||
listid = sys.argv[3]
|
||||
cardid = sys.argv[4]
|
||||
labelIds = sys.argv[5]
|
||||
edcard = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + s + cardid
|
||||
print(edcard)
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
put_data = {'labelIds': labelIds}
|
||||
body = requests.put(edcard, data=put_data, headers=headers)
|
||||
print("=== ADD LABEL ===\n")
|
||||
body = requests.get(edcard, headers=headers)
|
||||
data2 = body.text.replace('}',"}\n")
|
||||
print(data2)
|
||||
# ------- EDIT CARD ADD LABEL END -----------
|
||||
|
||||
if sys.argv[1] == 'editcardcolor':
|
||||
# ------- EDIT CARD COLOR START -----------
|
||||
boardid = sys.argv[2]
|
||||
listid = sys.argv[3]
|
||||
cardid = sys.argv[4]
|
||||
newcolor = sys.argv[5]
|
||||
|
||||
valid_colors = ['white', 'green', 'yellow', 'orange', 'red', 'purple', 'blue', 'sky', 'lime', 'pink', 'black',
|
||||
'silver', 'peachpuff', 'crimson', 'plum', 'darkgreen', 'slateblue', 'magenta', 'gold', 'navy',
|
||||
'gray', 'saddlebrown', 'paleturquoise', 'mistyrose', 'indigo']
|
||||
|
||||
if newcolor not in valid_colors:
|
||||
print("Invalid color. Choose a color from the list.")
|
||||
sys.exit(1)
|
||||
|
||||
edcard = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + s + cardid
|
||||
print(edcard)
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
put_data = {'color': '{}'.format(newcolor)}
|
||||
body = requests.put(edcard, data=put_data, headers=headers)
|
||||
print("=== EDIT CARD COLOR ===\n")
|
||||
body = requests.get(edcard, headers=headers)
|
||||
data2 = body.text.replace('}', "}\n")
|
||||
print(data2)
|
||||
# ------- EDIT CARD COLOR END -----------
|
||||
|
||||
if arguments >= 4:
|
||||
|
||||
if sys.argv[1] == 'newuser':
|
||||
|
||||
@@ -251,9 +358,155 @@ if arguments == 4:
|
||||
print("=== CREATE NEW USER ===\n")
|
||||
print(body.text)
|
||||
# ------- CREATE NEW USER END -----------
|
||||
|
||||
if sys.argv[1] == 'getcard':
|
||||
|
||||
# ------- LIST OF CARD START -----------
|
||||
boardid = sys.argv[2]
|
||||
listid = sys.argv[3]
|
||||
cardid = sys.argv[4]
|
||||
listone = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + s + cardid
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
print("=== INFO OF ONE LIST ===\n")
|
||||
print("URL:", listone) # Stampa l'URL per debug
|
||||
try:
|
||||
response = requests.get(listone, headers=headers)
|
||||
print("=== RESPONSE ===\n")
|
||||
print("Status Code:", response.status_code) # Stampa il codice di stato per debug
|
||||
|
||||
if response.status_code == 200:
|
||||
data2 = response.text.replace('}', "}\n")
|
||||
print(data2)
|
||||
else:
|
||||
print(f"Error: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"Error in the GET request: {e}")
|
||||
# ------- LISTS OF CARD END -----------
|
||||
|
||||
if sys.argv[1] == 'createlabel':
|
||||
|
||||
# ------- CREATE LABEL START -----------
|
||||
boardid = sys.argv[2]
|
||||
labelcolor = sys.argv[3]
|
||||
labelname = sys.argv[4]
|
||||
label_url = wekanurl + apiboards + boardid + s + 'labels'
|
||||
print(label_url)
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
# Object to send
|
||||
put_data = {'label': {'color': labelcolor, 'name': labelname}}
|
||||
print("URL:", label_url)
|
||||
print("Headers:", headers)
|
||||
print("Data:", put_data)
|
||||
try:
|
||||
response = requests.put(label_url, json=put_data, headers=headers)
|
||||
print("=== CREATE LABELS ===\n")
|
||||
print("Response Status Code:", response.status_code)
|
||||
print("Response Text:", response.text)
|
||||
except Exception as e:
|
||||
print("Error:", e)
|
||||
# ------- CREATE LABEL END -----------
|
||||
|
||||
if sys.argv[1] == 'addchecklist':
|
||||
|
||||
# ------- ADD CHECKLIST START -----------
|
||||
board_id = sys.argv[2]
|
||||
card_id = sys.argv[3]
|
||||
checklist_title = sys.argv[4]
|
||||
|
||||
# Aggiungi la checklist
|
||||
checklist_url = wekanurl + apiboards + board_id + s + cs + s + card_id + '/checklists'
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
data = {'title': checklist_title}
|
||||
|
||||
response = requests.post(checklist_url, data=data, headers=headers)
|
||||
response.raise_for_status()
|
||||
|
||||
result = json.loads(response.text)
|
||||
checklist_id = result.get('_id')
|
||||
|
||||
print(f"Checklist '{checklist_title}' created. ID: {checklist_id}")
|
||||
|
||||
# Aggiungi gli items alla checklist
|
||||
items_to_add = sys.argv[5:]
|
||||
for item_title in items_to_add:
|
||||
checklist_item_url = wekanurl + apiboards + board_id + s + cs + s + card_id + s + 'checklists' + s + checklist_id + '/items'
|
||||
item_data = {'title': item_title}
|
||||
|
||||
item_response = requests.post(checklist_item_url, data=item_data, headers=headers)
|
||||
item_response.raise_for_status()
|
||||
|
||||
item_result = json.loads(item_response.text)
|
||||
checklist_item_id = item_result.get('_id')
|
||||
|
||||
print(f"Item '{item_title}' added. ID: {checklist_item_id}")
|
||||
|
||||
if sys.argv[1] == 'checklistinfo':
|
||||
|
||||
# ------- ADD CHECKLIST START -----------
|
||||
board_id = sys.argv[2]
|
||||
card_id = sys.argv[3]
|
||||
checklist_id = sys.argv[4]
|
||||
checklist_url = wekanurl + apiboards + board_id + s + cs + s + card_id + '/checklists' + s + checklist_id
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
response = requests.get(checklist_url, headers=headers)
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
checklist_info = response.json()
|
||||
print("Checklist Info:")
|
||||
print(checklist_info)
|
||||
|
||||
if arguments == 3:
|
||||
|
||||
if sys.argv[1] == 'editboardtitle':
|
||||
|
||||
# ------- EDIT BOARD TITLE START -----------
|
||||
boardid = sys.argv[2]
|
||||
boardtitle = sys.argv[3]
|
||||
edboardtitle = wekanurl + apiboards + boardid + s + 'title'
|
||||
print(edboardtitle)
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
|
||||
post_data = {'title': boardtitle}
|
||||
|
||||
body = requests.put(edboardtitle, json=post_data, headers=headers)
|
||||
print("=== EDIT BOARD TITLE ===\n")
|
||||
#body = requests.get(edboardtitle, headers=headers)
|
||||
data2 = body.text.replace('}',"}\n")
|
||||
print(data2)
|
||||
if body.status_code == 200:
|
||||
print("Succesfull!")
|
||||
else:
|
||||
print(f"Error: {body.status_code}")
|
||||
print(body.text)
|
||||
|
||||
# ------- EDIT BOARD TITLE END -----------
|
||||
|
||||
if sys.argv[1] == 'copyboard':
|
||||
|
||||
# ------- COPY BOARD START -----------
|
||||
boardid = sys.argv[2]
|
||||
boardtitle = sys.argv[3]
|
||||
edboardcopy = wekanurl + apiboards + boardid + s + 'copy'
|
||||
print(edboardcopy)
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
|
||||
post_data = {'title': boardtitle}
|
||||
|
||||
body = requests.post(edboardcopy, json=post_data, headers=headers)
|
||||
print("=== COPY BOARD ===\n")
|
||||
#body = requests.get(edboardcopy, headers=headers)
|
||||
data2 = body.text.replace('}',"}\n")
|
||||
print(data2)
|
||||
if body.status_code == 200:
|
||||
print("Succesfull!")
|
||||
else:
|
||||
print(f"Error: {body.status_code}")
|
||||
print(body.text)
|
||||
|
||||
# ------- COPY BOARD END -----------
|
||||
|
||||
if sys.argv[1] == 'createlist':
|
||||
|
||||
# ------- CREATE LIST START -----------
|
||||
@@ -293,6 +546,90 @@ if arguments == 3:
|
||||
print(data2)
|
||||
# ------- INFO OF CUSTOM FIELD END -----------
|
||||
|
||||
if sys.argv[1] == 'cardsbyswimlane':
|
||||
# ------- RETRIEVE CARDS BY SWIMLANE ID START -----------
|
||||
boardid = sys.argv[2]
|
||||
swimlaneid = sys.argv[3]
|
||||
cardsbyswimlane = wekanurl + apiboards + boardid + s + sws + s + swimlaneid + s + cs
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
print("=== CARDS BY SWIMLANE ID ===\n")
|
||||
print("URL:", cardsbyswimlane) # Debug
|
||||
try:
|
||||
body = requests.get(cardsbyswimlane, headers=headers)
|
||||
print("Status Code:", body.status_code) # Debug
|
||||
data = body.text.replace('}', "}\n")
|
||||
print("Data:", data)
|
||||
except Exception as e:
|
||||
print("Error GET:", e)
|
||||
# ------- RETRIEVE CARDS BY SWIMLANE ID END -----------
|
||||
|
||||
if sys.argv[1] == 'deleteallcards':
|
||||
boardid = sys.argv[2]
|
||||
swimlaneid = sys.argv[3]
|
||||
|
||||
# ------- GET SWIMLANE CARDS START -----------
|
||||
get_swimlane_cards_url = wekanurl + apiboards + boardid + s + "swimlanes" + s + swimlaneid + s + "cards"
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
|
||||
try:
|
||||
response = requests.get(get_swimlane_cards_url, headers=headers)
|
||||
response.raise_for_status()
|
||||
cards_data = response.json()
|
||||
|
||||
# Print the details of each card
|
||||
for card in cards_data:
|
||||
# ------- DELETE CARD START -----------
|
||||
delete_card_url = wekanurl + apiboards + boardid + s + "lists" + s + card['listId'] + s + "cards" + s + card['_id']
|
||||
try:
|
||||
response = requests.delete(delete_card_url, headers=headers)
|
||||
if response.status_code == 404:
|
||||
print(f"Card not found: {card['_id']}")
|
||||
else:
|
||||
response.raise_for_status()
|
||||
deleted_card_data = response.json()
|
||||
print(f"Card Deleted Successfully. Card ID: {deleted_card_data['_id']}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error deleting card: {e}")
|
||||
# ------- DELETE CARD END -----------
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error getting swimlane cards: {e}")
|
||||
sys.exit(1)
|
||||
# ------- GET SWIMLANE CARDS END -----------
|
||||
|
||||
if sys.argv[1] == 'get_list_cards_count':
|
||||
# ------- GET LIST CARDS COUNT START -----------
|
||||
boardid = sys.argv[2]
|
||||
listid = sys.argv[3]
|
||||
|
||||
get_list_cards_count_url = wekanurl + apiboards + boardid + s + l + s + listid + s + "cards_count"
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
|
||||
try:
|
||||
response = requests.get(get_list_cards_count_url, headers=headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
print(f"List Cards Count: {data['list_cards_count']}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error: {e}")
|
||||
# ------- GET LIST CARDS COUNT END -----------
|
||||
|
||||
if sys.argv[1] == 'checklistid':
|
||||
|
||||
# ------- ADD CHECKLIST START -----------
|
||||
board_id = sys.argv[2]
|
||||
card_id = sys.argv[3]
|
||||
|
||||
checklist_url = wekanurl + apiboards + board_id + s + cs + s + card_id + '/checklists'
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
response = requests.get(checklist_url, headers=headers)
|
||||
|
||||
response.raise_for_status()
|
||||
checklists = response.json()
|
||||
print("Checklists:")
|
||||
for checklist in checklists:
|
||||
print(checklist)
|
||||
|
||||
if arguments == 2:
|
||||
|
||||
# ------- BOARDS LIST START -----------
|
||||
@@ -364,6 +701,22 @@ if arguments == 2:
|
||||
print(data2)
|
||||
# ------- LISTS OF ATTACHMENTS END -----------
|
||||
|
||||
if sys.argv[1] == 'get_board_cards_count':
|
||||
# ------- GET BOARD CARDS COUNT START -----------
|
||||
boardid = sys.argv[2]
|
||||
|
||||
get_board_cards_count_url = wekanurl + apiboards + boardid + s + "cards_count"
|
||||
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
|
||||
|
||||
try:
|
||||
response = requests.get(get_board_cards_count_url, headers=headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
print(f"Board Cards Count: {data['board_cards_count']}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error: {e}")
|
||||
# ------- GET BOARD CARDS COUNT END -----------
|
||||
|
||||
if arguments == 1:
|
||||
|
||||
if sys.argv[1] == 'users':
|
||||
|
||||
@@ -49,43 +49,6 @@
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions {
|
||||
display: flex;
|
||||
margin-top: 5px;
|
||||
gap: 5px;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions .open-comment-reaction-popup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
height: 24px;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions .open-comment-reaction-popup i.fa.fa-smile-o {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
margin-left: 2px;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions .open-comment-reaction-popup i.fa.fa-plus {
|
||||
font-size: 8px;
|
||||
margin-top: -7px;
|
||||
margin-left: 1px;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions .reaction {
|
||||
cursor: pointer;
|
||||
border: 1px solid #808080;
|
||||
border-radius: 15px;
|
||||
display: flex;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions .reaction.selected {
|
||||
background-color: #b0c4de;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions .reaction:hover {
|
||||
background-color: #b0c4de;
|
||||
}
|
||||
.activities .activity .activity-desc .reactions .reaction .reaction-count {
|
||||
font-size: 12px;
|
||||
}
|
||||
.activities .activity .activity-desc .activity-checklist {
|
||||
display: block;
|
||||
border-radius: 3px;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
template(name="activities")
|
||||
.activities.js-sidebar-activities
|
||||
//- We should use Template.dynamic here but there is a bug with
|
||||
//- blaze-components: https://github.com/peerlibrary/meteor-blaze-components/issues/30
|
||||
if $eq mode "board"
|
||||
+boardActivities
|
||||
else
|
||||
+cardActivities
|
||||
if showActivities
|
||||
.activities.js-sidebar-activities
|
||||
//- We should use Template.dynamic here but there is a bug with
|
||||
//- blaze-components: https://github.com/peerlibrary/meteor-blaze-components/issues/30
|
||||
if $eq mode "board"
|
||||
+boardActivities
|
||||
else
|
||||
+cardActivities
|
||||
|
||||
template(name="boardActivities")
|
||||
each activityData in currentBoard.activities
|
||||
@@ -15,32 +16,6 @@ template(name="cardActivities")
|
||||
each activityData in activities
|
||||
+activity(activity=activityData card=card mode=mode)
|
||||
|
||||
template(name="editOrDeleteComment")
|
||||
= ' - '
|
||||
a.js-open-inlined-form {{_ "edit"}}
|
||||
= ' - '
|
||||
a.js-delete-comment {{_ "delete"}}
|
||||
|
||||
template(name="deleteCommentPopup")
|
||||
p {{_ "comment-delete"}}
|
||||
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
|
||||
|
||||
template(name="commentReactions")
|
||||
.reactions
|
||||
each reaction in reactions
|
||||
span.reaction(class="{{#if isSelected reaction.userIds}}selected{{/if}}" data-codepoint="#{reaction.reactionCodepoint}" title="{{userNames reaction.userIds}}")
|
||||
span.reaction-codepoint !{reaction.reactionCodepoint}
|
||||
span.reaction-count #{reaction.userIds.length}
|
||||
if (currentUser.isBoardMember)
|
||||
a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}")
|
||||
i.fa.fa-smile-o
|
||||
i.fa.fa-plus
|
||||
|
||||
template(name="addReactionPopup")
|
||||
.reactions-popup
|
||||
each codepoint in codepoints
|
||||
span.add-comment-reaction(data-codepoint="#{codepoint}") !{codepoint}
|
||||
|
||||
template(name="activity")
|
||||
.activity(data-id=activity._id)
|
||||
+userAvatar(userId=activity.user._id)
|
||||
@@ -130,39 +105,17 @@ template(name="activity")
|
||||
| {{{_ 'activity-checklist-item-removed' (sanitize activity.checklist.title) cardLink}}}.
|
||||
|
||||
//- comment activity ----------------------------------------------------
|
||||
if($eq mode 'card')
|
||||
//- if we are in card mode we display the comment in a way that it
|
||||
//- can be edited by the owner
|
||||
if($eq activity.activityType 'addComment')
|
||||
+inlinedForm(classNames='js-edit-comment')
|
||||
+editor(autofocus=true)
|
||||
= activity.comment.text
|
||||
.edit-controls
|
||||
button.primary(type="submit") {{_ 'edit'}}
|
||||
.fa.fa-times-thin.js-close-inlined-form
|
||||
else
|
||||
.activity-comment
|
||||
+viewer
|
||||
= activity.comment.text
|
||||
+commentReactions(reactions=activity.comment.reactions commentId=activity.comment._id)
|
||||
span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
|
||||
if($eq currentUser._id activity.comment.userId)
|
||||
+editOrDeleteComment
|
||||
else if currentUser.isBoardAdmin
|
||||
+editOrDeleteComment
|
||||
if($eq activity.activityType 'deleteComment')
|
||||
| {{{_ 'activity-deleteComment' activity.commentId}}}.
|
||||
|
||||
if($eq activity.activityType 'deleteComment')
|
||||
| {{{_ 'activity-deleteComment' activity.commentId}}}.
|
||||
if($eq activity.activityType 'editComment')
|
||||
| {{{_ 'activity-editComment' activity.commentId}}}.
|
||||
|
||||
if($eq activity.activityType 'editComment')
|
||||
| {{{_ 'activity-editComment' activity.commentId}}}.
|
||||
else
|
||||
//- if we are not in card mode we only display a summary of the comment
|
||||
if($eq activity.activityType 'addComment')
|
||||
| {{{_ 'activity-on' cardLink}}}
|
||||
a.activity-comment(href="{{ activity.card.originRelativeUrl }}")
|
||||
+viewer
|
||||
= activity.comment.text
|
||||
if($eq activity.activityType 'addComment')
|
||||
| {{{_ 'activity-on' cardLink}}}
|
||||
a.activity-comment(href="{{ activity.card.originRelativeUrl }}")
|
||||
+viewer
|
||||
= activity.comment.text
|
||||
|
||||
//- date activity ------------------------------------------------
|
||||
if($eq activity.activityType 'a-receivedAt')
|
||||
@@ -208,6 +161,9 @@ template(name="activity")
|
||||
if($eq activity.activityType 'archivedList')
|
||||
| {{_ 'activity-archived' (sanitize listLabel)}}.
|
||||
|
||||
if($eq activity.activityType 'changedListTitle')
|
||||
| {{_ 'activity-changedListTitle' (sanitize listLabel) boardLabelLink}}
|
||||
|
||||
//- member activity ----------------------------------------------------
|
||||
if($eq activity.activityType 'joinMember')
|
||||
if($eq user._id activity.member._id)
|
||||
@@ -243,4 +199,4 @@ template(name="activity")
|
||||
else if(currentData.timeValue)
|
||||
| {{_ activity.activityType currentData.timeValue}}
|
||||
|
||||
span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
|
||||
div(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
|
||||
@@ -13,39 +13,41 @@ BlazeComponent.extendComponent({
|
||||
const sidebar = Sidebar;
|
||||
sidebar && sidebar.callFirstWith(null, 'resetNextPeak');
|
||||
this.autorun(() => {
|
||||
let mode = this.data().mode;
|
||||
const capitalizedMode = Utils.capitalize(mode);
|
||||
let searchId;
|
||||
if (mode === 'linkedcard' || mode === 'linkedboard') {
|
||||
searchId = Utils.getCurrentCard().linkedId;
|
||||
mode = mode.replace('linked', '');
|
||||
} else if (mode === 'card') {
|
||||
searchId = Utils.getCurrentCardId();
|
||||
} else {
|
||||
searchId = Session.get(`current${capitalizedMode}`);
|
||||
}
|
||||
const limit = this.page.get() * activitiesPerPage;
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const hideSystem = user ? user.hasHiddenSystemMessages() : false;
|
||||
if (searchId === null) return;
|
||||
|
||||
this.subscribe('activities', mode, searchId, limit, hideSystem, () => {
|
||||
this.loadNextPageLocked = false;
|
||||
|
||||
// TODO the guard can be removed as soon as the TODO above is resolved
|
||||
if (!sidebar) return;
|
||||
// If the sibear peak hasn't increased, that mean that there are no more
|
||||
// activities, and we can stop calling new subscriptions.
|
||||
// XXX This is hacky! We need to know excatly and reactively how many
|
||||
// activities there are, we probably want to denormalize this number
|
||||
// dirrectly into card and board documents.
|
||||
const nextPeakBefore = sidebar.callFirstWith(null, 'getNextPeak');
|
||||
sidebar.calculateNextPeak();
|
||||
const nextPeakAfter = sidebar.callFirstWith(null, 'getNextPeak');
|
||||
if (nextPeakBefore === nextPeakAfter) {
|
||||
sidebar.callFirstWith(null, 'resetNextPeak');
|
||||
let mode = this.data()?.mode;
|
||||
if (mode) {
|
||||
const capitalizedMode = Utils.capitalize(mode);
|
||||
let searchId;
|
||||
const showActivities = this.showActivities();
|
||||
if (mode === 'linkedcard' || mode === 'linkedboard') {
|
||||
const currentCard = Utils.getCurrentCard();
|
||||
searchId = currentCard.linkedId;
|
||||
mode = mode.replace('linked', '');
|
||||
} else if (mode === 'card') {
|
||||
searchId = Utils.getCurrentCardId();
|
||||
} else {
|
||||
searchId = Session.get(`current${capitalizedMode}`);
|
||||
}
|
||||
});
|
||||
const limit = this.page.get() * activitiesPerPage;
|
||||
if (searchId === null) return;
|
||||
|
||||
this.subscribe('activities', mode, searchId, limit, showActivities, () => {
|
||||
this.loadNextPageLocked = false;
|
||||
|
||||
// TODO the guard can be removed as soon as the TODO above is resolved
|
||||
if (!sidebar) return;
|
||||
// If the sibear peak hasn't increased, that mean that there are no more
|
||||
// activities, and we can stop calling new subscriptions.
|
||||
// XXX This is hacky! We need to know excatly and reactively how many
|
||||
// activities there are, we probably want to denormalize this number
|
||||
// dirrectly into card and board documents.
|
||||
const nextPeakBefore = sidebar.callFirstWith(null, 'getNextPeak');
|
||||
sidebar.calculateNextPeak();
|
||||
const nextPeakAfter = sidebar.callFirstWith(null, 'getNextPeak');
|
||||
if (nextPeakBefore === nextPeakAfter) {
|
||||
sidebar.callFirstWith(null, 'resetNextPeak');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
loadNextPage() {
|
||||
@@ -54,14 +56,26 @@ BlazeComponent.extendComponent({
|
||||
this.loadNextPageLocked = true;
|
||||
}
|
||||
},
|
||||
}).register('activities');
|
||||
|
||||
Template.activities.helpers({
|
||||
activities() {
|
||||
const ret = this.card.activities();
|
||||
showActivities() {
|
||||
let ret = false;
|
||||
let mode = this.data()?.mode;
|
||||
if (mode) {
|
||||
if (mode === 'linkedcard' || mode === 'linkedboard') {
|
||||
const currentCard = Utils.getCurrentCard();
|
||||
ret = currentCard.showActivities ?? false;
|
||||
} else if (mode === 'card') {
|
||||
ret = this.data()?.card?.showActivities ?? false;
|
||||
} else {
|
||||
ret = Utils.getCurrentBoard().showActivities ?? false;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
});
|
||||
activities() {
|
||||
const ret = this.data().card.activities();
|
||||
return ret;
|
||||
},
|
||||
}).register('activities');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
checkItem() {
|
||||
@@ -247,32 +261,6 @@ BlazeComponent.extendComponent({
|
||||
return customField.name;
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
// XXX We should use Popup.afterConfirmation here
|
||||
'click .js-delete-comment': Popup.afterConfirm('deleteComment', () => {
|
||||
const commentId = this.data().activity.commentId;
|
||||
CardComments.remove(commentId);
|
||||
Popup.back();
|
||||
}),
|
||||
'submit .js-edit-comment'(evt) {
|
||||
evt.preventDefault();
|
||||
const commentText = this.currentComponent()
|
||||
.getValue()
|
||||
.trim();
|
||||
const commentId = Template.parentData().activity.commentId;
|
||||
if (commentText) {
|
||||
CardComments.update(commentId, {
|
||||
$set: {
|
||||
text: commentText,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('activity');
|
||||
|
||||
Template.activity.helpers({
|
||||
|
||||
@@ -63,3 +63,78 @@
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
.comments {
|
||||
clear: both;
|
||||
}
|
||||
.comments .comment {
|
||||
margin: 0.5px 0;
|
||||
padding: 6px 0;
|
||||
display: flex;
|
||||
}
|
||||
.comments .comment .member {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
.comments .comment .comment-member {
|
||||
font-weight: 700;
|
||||
}
|
||||
.comments .comment .comment-desc {
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
margin: 0;
|
||||
margin-left: 3px;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
}
|
||||
.comments .comment .comment-desc .comment-text {
|
||||
display: block;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions {
|
||||
display: flex;
|
||||
margin-top: 5px;
|
||||
gap: 5px;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .open-comment-reaction-popup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
height: 24px;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-smile-o {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
margin-left: 2px;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-plus {
|
||||
font-size: 8px;
|
||||
margin-top: -7px;
|
||||
margin-left: 1px;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .reaction {
|
||||
cursor: pointer;
|
||||
border: 1px solid #808080;
|
||||
border-radius: 15px;
|
||||
display: flex;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .reaction.selected {
|
||||
background-color: #b0c4de;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .reaction:hover {
|
||||
background-color: #b0c4de;
|
||||
}
|
||||
.comments .comment .comment-desc .reactions .reaction .reaction-count {
|
||||
font-size: 12px;
|
||||
}
|
||||
.comments .comment .comment-desc .comment-meta {
|
||||
font-size: 0.8em;
|
||||
color: #999;
|
||||
}
|
||||
@@ -7,3 +7,59 @@ template(name="commentForm")
|
||||
| {{getUnsavedValue 'cardComment' currentCard._id}}
|
||||
.add-controls
|
||||
button.primary.confirm.clear.js-add-comment(type="submit") {{_ 'comment'}}
|
||||
|
||||
template(name="comments")
|
||||
.comments
|
||||
each commentData in getComments
|
||||
+comment(commentData)
|
||||
|
||||
template(name="comment")
|
||||
.comment
|
||||
+userAvatar(userId=userId)
|
||||
p.comment-desc
|
||||
span.comment-member
|
||||
+memberName(user=user)
|
||||
|
||||
+inlinedForm(classNames='js-edit-comment')
|
||||
+editor(autofocus=true)
|
||||
= text
|
||||
.edit-controls
|
||||
button.primary(type="submit") {{_ 'edit'}}
|
||||
.fa.fa-times-thin.js-close-inlined-form
|
||||
else
|
||||
.comment-text
|
||||
+viewer
|
||||
= text
|
||||
+commentReactions(reactions=reactions commentId=_id)
|
||||
span(title=createdAt).comment-meta {{ moment createdAt }}
|
||||
if($eq currentUser._id userId)
|
||||
+editOrDeleteComment
|
||||
else if currentUser.isBoardAdmin
|
||||
+editOrDeleteComment
|
||||
|
||||
template(name="editOrDeleteComment")
|
||||
= ' - '
|
||||
a.js-open-inlined-form {{_ "edit"}}
|
||||
= ' - '
|
||||
a.js-delete-comment {{_ "delete"}}
|
||||
|
||||
template(name="deleteCommentPopup")
|
||||
p {{_ "comment-delete"}}
|
||||
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
|
||||
|
||||
template(name="commentReactions")
|
||||
.reactions
|
||||
each reaction in reactions
|
||||
span.reaction(class="{{#if isSelected reaction.userIds}}selected{{/if}}" data-codepoint="#{reaction.reactionCodepoint}" title="{{userNames reaction.userIds}}")
|
||||
span.reaction-codepoint !{reaction.reactionCodepoint}
|
||||
span.reaction-count #{reaction.userIds.length}
|
||||
if (currentUser.isBoardMember)
|
||||
a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}")
|
||||
i.fa.fa-smile-o
|
||||
i.fa.fa-plus
|
||||
|
||||
template(name="addReactionPopup")
|
||||
.reactions-popup
|
||||
each codepoint in codepoints
|
||||
span.add-comment-reaction(data-codepoint="#{codepoint}") !{codepoint}
|
||||
|
||||
@@ -55,6 +55,41 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
}).register('commentForm');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
getComments() {
|
||||
const ret = this.data().comments();
|
||||
return ret;
|
||||
},
|
||||
}).register("comments");
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-delete-comment': Popup.afterConfirm('deleteComment', () => {
|
||||
const commentId = this.data()._id;
|
||||
CardComments.remove(commentId);
|
||||
Popup.back();
|
||||
}),
|
||||
'submit .js-edit-comment'(evt) {
|
||||
evt.preventDefault();
|
||||
const commentText = this.currentComponent()
|
||||
.getValue()
|
||||
.trim();
|
||||
const commentId = this.data()._id;
|
||||
if (commentText) {
|
||||
CardComments.update(commentId, {
|
||||
$set: {
|
||||
text: commentText,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register("comment");
|
||||
|
||||
// XXX This should be a static method of the `commentForm` component
|
||||
function resetCommentInput(input) {
|
||||
input.val(''); // without manually trigger, input event won't be fired
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
transition: margin 0.1s;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.board-wrapper .board-canvas.is-sibling-sidebar-open {
|
||||
margin-right: 248px;
|
||||
}
|
||||
.board-wrapper .board-canvas .board-overlay {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
|
||||
@@ -17,25 +17,32 @@ template(name="boardBody")
|
||||
| {{_ 'tableVisibilityMode-allowPrivateOnly'}}
|
||||
else
|
||||
.board-wrapper(class=currentBoard.colorClass)
|
||||
+sidebar
|
||||
.board-canvas.js-swimlanes(
|
||||
class="{{#if hasSwimlanes}}dragscroll{{/if}}"
|
||||
class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}"
|
||||
class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}"
|
||||
class="{{#if draggingActive.get}}is-dragging-active{{/if}}")
|
||||
class="{{#if draggingActive.get}}is-dragging-active{{/if}}"
|
||||
class="{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}")
|
||||
if showOverlay.get
|
||||
.board-overlay
|
||||
if currentBoard.isTemplatesBoard
|
||||
each currentBoard.swimlanes
|
||||
+swimlane(this)
|
||||
else if isViewSwimlanes
|
||||
each currentBoard.swimlanes
|
||||
+swimlane(this)
|
||||
if hasSwimlanes
|
||||
each currentBoard.swimlanes
|
||||
+swimlane(this)
|
||||
else
|
||||
a.js-empty-board-add-swimlane(title="{{_ 'add-swimlane'}}")
|
||||
h1.big-message.quiet
|
||||
| {{_ 'add-swimlane'}} +
|
||||
else if isViewLists
|
||||
+listsGroup(currentBoard)
|
||||
else if isViewCalendar
|
||||
+calendarView
|
||||
else
|
||||
+listsGroup(currentBoard)
|
||||
+sidebar
|
||||
|
||||
template(name="calendarView")
|
||||
if isViewCalendar
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import dragscroll from '@wekanteam/dragscroll';
|
||||
|
||||
const subManager = new SubsManager();
|
||||
const { calculateIndex } = Utils;
|
||||
@@ -194,6 +195,9 @@ BlazeComponent.extendComponent({
|
||||
});
|
||||
|
||||
this.autorun(() => {
|
||||
// Always reset dragscroll on view switch
|
||||
dragscroll.reset();
|
||||
|
||||
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
|
||||
$swimlanesDom.sortable({
|
||||
handle: '.js-swimlane-header-handle',
|
||||
@@ -219,6 +223,7 @@ BlazeComponent.extendComponent({
|
||||
boardComponent.openNewListForm();
|
||||
}
|
||||
|
||||
dragscroll.reset();
|
||||
Utils.setBackgroundImage();
|
||||
},
|
||||
|
||||
@@ -243,6 +248,10 @@ BlazeComponent.extendComponent({
|
||||
}
|
||||
},
|
||||
|
||||
hasSwimlanes() {
|
||||
return Utils.getCurrentBoard().swimlanes().length > 0;
|
||||
},
|
||||
|
||||
isViewLists() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser) {
|
||||
@@ -261,6 +270,11 @@ BlazeComponent.extendComponent({
|
||||
}
|
||||
},
|
||||
|
||||
isVerticalScrollbars() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
return user && user.isVerticalScrollbars();
|
||||
},
|
||||
|
||||
openNewListForm() {
|
||||
if (this.isViewSwimlanes()) {
|
||||
// The form had been removed in 416b17062e57f215206e93a85b02ef9eb1ab4902
|
||||
@@ -283,6 +297,7 @@ BlazeComponent.extendComponent({
|
||||
this._isDragging = false;
|
||||
}
|
||||
},
|
||||
'click .js-empty-board-add-swimlane': Popup.open('swimlaneAdd'),
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -12,8 +12,9 @@ template(name="boardHeaderBar")
|
||||
if currentBoard
|
||||
if currentUser
|
||||
with currentBoard
|
||||
a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title)
|
||||
i.fa.fa-pencil-square-o
|
||||
if currentUser.isBoardAdmin
|
||||
a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title)
|
||||
i.fa.fa-pencil-square-o
|
||||
|
||||
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
|
||||
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
|
||||
|
||||
@@ -1,42 +1,12 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import dragscroll from '@wekanteam/dragscroll';
|
||||
|
||||
/*
|
||||
const DOWNCLS = 'fa-sort-down';
|
||||
const UPCLS = 'fa-sort-up';
|
||||
*/
|
||||
const sortCardsBy = new ReactiveVar('');
|
||||
Template.boardMenuPopup.events({
|
||||
'click .js-rename-board': Popup.open('boardChangeTitle'),
|
||||
'click .js-custom-fields'() {
|
||||
Sidebar.setView('customFields');
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-open-archives'() {
|
||||
Sidebar.setView('archives');
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-change-board-color': Popup.open('boardChangeColor'),
|
||||
'click .js-change-language': Popup.open('changeLanguage'),
|
||||
'click .js-archive-board ': Popup.afterConfirm('archiveBoard', function() {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
currentBoard.archive();
|
||||
// XXX We should have some kind of notification on top of the page to
|
||||
// confirm that the board was successfully archived.
|
||||
FlowRouter.go('home');
|
||||
}),
|
||||
'click .js-delete-board': Popup.afterConfirm('deleteBoard', function() {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
Popup.back();
|
||||
Boards.remove(currentBoard._id);
|
||||
FlowRouter.go('home');
|
||||
}),
|
||||
'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
|
||||
'click .js-import-board': Popup.open('chooseBoardSource'),
|
||||
'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
|
||||
'click .js-card-settings': Popup.open('boardCardSettings'),
|
||||
'click .js-minicard-settings': Popup.open('boardMinicardSettings'),
|
||||
});
|
||||
|
||||
Template.boardChangeTitlePopup.events({
|
||||
submit(event, templateInstance) {
|
||||
|
||||
@@ -151,8 +151,8 @@ BlazeComponent.extendComponent({
|
||||
}
|
||||
const currUser = ReactiveCache.getCurrentUser();
|
||||
|
||||
let orgIdsUserBelongs = currUser !== undefined && currUser.teams !== 'undefined' ? currUser.orgIdsUserBelongs() : '';
|
||||
if (orgIdsUserBelongs && orgIdsUserBelongs != '') {
|
||||
let orgIdsUserBelongs = currUser?.orgIdsUserBelongs() || '';
|
||||
if (orgIdsUserBelongs) {
|
||||
let orgsIds = orgIdsUserBelongs.split(',');
|
||||
// for(let i = 0; i < orgsIds.length; i++){
|
||||
// query.$and[2].$or.push({'orgs.orgId': orgsIds[i]});
|
||||
@@ -162,8 +162,8 @@ BlazeComponent.extendComponent({
|
||||
query.$and[2].$or.push({ 'orgs.orgId': { $in: orgsIds } });
|
||||
}
|
||||
|
||||
let teamIdsUserBelongs = currUser !== undefined && currUser.teams !== 'undefined' ? currUser.teamIdsUserBelongs() : '';
|
||||
if (teamIdsUserBelongs && teamIdsUserBelongs != '') {
|
||||
let teamIdsUserBelongs = currUser?.teamIdsUserBelongs() || '';
|
||||
if (teamIdsUserBelongs) {
|
||||
let teamsIds = teamIdsUserBelongs.split(',');
|
||||
// for(let i = 0; i < teamsIds.length; i++){
|
||||
// query.$or[2].$or.push({'teams.teamId': teamsIds[i]});
|
||||
@@ -199,15 +199,12 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
boardMembers(boardId) {
|
||||
let boardMembers = [];
|
||||
/* Bug Board icons random dance https://github.com/wekan/wekan/issues/4214
|
||||
const lists = ReactiveCache.getBoard(boardId)
|
||||
let members = lists.members
|
||||
members.forEach(member => {
|
||||
boardMembers.push(member.userId);
|
||||
});
|
||||
*/
|
||||
const boardMembers = lists?.members.map(member => member.userId);
|
||||
return boardMembers;
|
||||
*/
|
||||
return [];
|
||||
},
|
||||
|
||||
isStarred() {
|
||||
|
||||
@@ -51,11 +51,7 @@ template(name="attachmentGallery")
|
||||
|
||||
.attachment-gallery
|
||||
|
||||
if currentUser.isAdmin
|
||||
a.attachment-item.add-attachment.js-add-attachment
|
||||
i.fa.fa-plus.icon
|
||||
|
||||
if currentUser.isNormal
|
||||
if canModifyCard
|
||||
a.attachment-item.add-attachment.js-add-attachment
|
||||
i.fa.fa-plus.icon
|
||||
|
||||
@@ -121,8 +117,6 @@ template(name="attachmentActionsPopup")
|
||||
| {{_ 'remove-background-image'}}
|
||||
else
|
||||
| {{_ 'add-background-image'}}
|
||||
p.attachment-storage
|
||||
| {{versions.original.storage}}
|
||||
|
||||
if $neq versions.original.storage "fs"
|
||||
a.js-move-storage-fs
|
||||
|
||||
@@ -39,7 +39,7 @@ Template.attachmentGallery.events({
|
||||
'click .js-rename': Popup.open('attachmentRename'),
|
||||
'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete', function() {
|
||||
Attachments.remove(this._id);
|
||||
Popup.back(2);
|
||||
Popup.back();
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -501,7 +501,7 @@ BlazeComponent.extendComponent({
|
||||
if (name === DOMPurify.sanitize(name)) {
|
||||
Meteor.call('renameAttachment', this.data()._id, name);
|
||||
}
|
||||
Popup.back(2);
|
||||
Popup.back();
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
float: left;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
margin: 0 4px 4px 0;
|
||||
margin: .3vh;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
z-index: 1;
|
||||
@@ -272,7 +272,7 @@
|
||||
box-sizing: border-box;
|
||||
top: 97px;
|
||||
left: 0px;
|
||||
height: calc(100% - 20px);
|
||||
height: calc(100% - 100px);
|
||||
width: calc(100% - 20px);
|
||||
float: left;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ template(name="cardDetails")
|
||||
|
||||
+attachmentViewer
|
||||
|
||||
section.card-details.js-card-details(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}'): .card-details-canvas
|
||||
section.card-details.js-card-details.nodragscroll(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}' class='{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}'): .card-details-canvas
|
||||
.card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}')
|
||||
+inlinedForm(classNames="js-card-details-title")
|
||||
+editCardTitleForm
|
||||
@@ -13,27 +13,12 @@ template(name="cardDetails")
|
||||
unless isMiniScreen
|
||||
unless isPopup
|
||||
a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
|
||||
if isAdmin
|
||||
if canModifyCard
|
||||
if cardMaximized
|
||||
a.fa.fa-window-minimize.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}")
|
||||
else
|
||||
a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
|
||||
if isNormal
|
||||
if cardMaximized
|
||||
a.fa.fa-window-minimize.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}")
|
||||
else
|
||||
a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
|
||||
if isWorker
|
||||
if cardMaximized
|
||||
a.fa.fa-window-minimize.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}")
|
||||
else
|
||||
a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
|
||||
if isNoComments
|
||||
if cardMaximized
|
||||
a.fa.fa-window-minimize.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}")
|
||||
else
|
||||
a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
|
||||
if currentUser.isBoardMember
|
||||
if canModifyCard
|
||||
a.fa.fa-navicon.card-details-menu.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
|
||||
a.fa.fa-link.card-copy-button.js-copy-link(
|
||||
id="cardURL_copy"
|
||||
@@ -45,7 +30,7 @@ template(name="cardDetails")
|
||||
else
|
||||
unless isPopup
|
||||
a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
|
||||
if currentUser.isBoardMember
|
||||
if canModifyCard
|
||||
a.fa.fa-navicon.card-details-menu-mobile-web.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
|
||||
a.fa.fa-link.card-copy-mobile-button.js-copy-link(
|
||||
id="cardURL_copy"
|
||||
@@ -540,11 +525,12 @@ template(name="cardDetails")
|
||||
a.fa.fa-times-thin.js-close-inlined-form
|
||||
else
|
||||
if currentBoard.allowsDescriptionText
|
||||
a.js-open-inlined-form.right(title="{{_ 'edit'}}" value=title)
|
||||
a.js-open-inlined-form(title="{{_ 'edit'}}" value=title)
|
||||
i.fa.fa-pencil-square-o
|
||||
if getDescription
|
||||
+viewer
|
||||
= getDescription
|
||||
a.js-open-inlined-form(title="{{_ 'edit'}}" value=title)
|
||||
if getDescription
|
||||
+viewer
|
||||
= getDescription
|
||||
if (hasUnsavedValue 'cardDescription' _id)
|
||||
p.quiet
|
||||
| {{_ 'unsaved-description'}}
|
||||
@@ -563,7 +549,7 @@ template(name="cardDetails")
|
||||
.card-checklist-attachmentGallery.card-checklists
|
||||
if currentBoard.allowsChecklists
|
||||
hr
|
||||
+checklists(cardId = _id)
|
||||
+checklists(cardId = _id card = this)
|
||||
if currentBoard.allowsSubtasks
|
||||
hr
|
||||
+subtasks(cardId = _id)
|
||||
@@ -583,25 +569,34 @@ template(name="cardDetails")
|
||||
+attachmentGallery
|
||||
hr
|
||||
|
||||
unless currentUser.isNoComments
|
||||
.comment-title
|
||||
h3.card-details-item-title
|
||||
i.fa.fa-comment-o
|
||||
| {{_ 'comments'}}
|
||||
|
||||
if currentBoard.allowsComments
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isNoComments
|
||||
+commentForm
|
||||
+comments
|
||||
hr
|
||||
|
||||
.card-details-right
|
||||
|
||||
unless currentUser.isNoComments
|
||||
.activity-title
|
||||
h3.card-details-item-title
|
||||
i.fa.fa-history
|
||||
| {{ _ 'activity'}}
|
||||
| {{ _ 'activities'}}
|
||||
if currentUser.isBoardMember
|
||||
.material-toggle-switch(title="{{_ 'hide-system-messages'}}")
|
||||
//span.toggle-switch-title
|
||||
if hiddenSystemMessages
|
||||
input.toggle-switch(type="checkbox" id="toggleButton" checked="checked")
|
||||
.material-toggle-switch(title="{{_ 'show-activities'}}")
|
||||
if showActivities
|
||||
input.toggle-switch(type="checkbox" id="toggleShowActivitiesCard" checked="checked")
|
||||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleButton")
|
||||
label.toggle-label(for="toggleButton")
|
||||
if currentBoard.allowsComments
|
||||
if currentUser.isBoardMember
|
||||
unless currentUser.isNoComments
|
||||
+commentForm
|
||||
input.toggle-switch(type="checkbox" id="toggleShowActivitiesCard")
|
||||
label.toggle-label(for="toggleShowActivitiesCard")
|
||||
|
||||
unless currentUser.isNoComments
|
||||
if isLoaded.get
|
||||
if isLinkedCard
|
||||
|
||||
@@ -63,10 +63,6 @@ BlazeComponent.extendComponent({
|
||||
return card.findWatcher(Meteor.userId());
|
||||
},
|
||||
|
||||
hiddenSystemMessages() {
|
||||
return ReactiveCache.getCurrentUser().hasHiddenSystemMessages();
|
||||
},
|
||||
|
||||
customFieldsGrid() {
|
||||
return ReactiveCache.getCurrentUser().hasCustomFieldsGrid();
|
||||
},
|
||||
@@ -118,6 +114,11 @@ BlazeComponent.extendComponent({
|
||||
);
|
||||
},
|
||||
|
||||
isVerticalScrollbars() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
return user && user.isVerticalScrollbars();
|
||||
},
|
||||
|
||||
/** returns if the list id is the current list id
|
||||
* @param listId list id to check
|
||||
* @return is the list id the current list id ?
|
||||
@@ -377,8 +378,11 @@ BlazeComponent.extendComponent({
|
||||
Session.set('cardDetailsIsDragging', false);
|
||||
Session.set('cardDetailsIsMouseDown', false);
|
||||
},
|
||||
'click #toggleButton'() {
|
||||
Meteor.call('toggleSystemMessages');
|
||||
'click #toggleShowActivitiesCard'() {
|
||||
this.data().toggleShowActivities();
|
||||
},
|
||||
'click #toggleHideCheckedChecklistItems'() {
|
||||
this.data().toggleHideCheckedChecklistItems();
|
||||
},
|
||||
'click #toggleCustomFieldsGridButton'() {
|
||||
Meteor.call('toggleCustomFieldsGrid');
|
||||
@@ -846,13 +850,15 @@ BlazeComponent.extendComponent({
|
||||
'click .js-palette-color'() {
|
||||
this.currentColor.set(this.currentData().color);
|
||||
},
|
||||
'click .js-submit'() {
|
||||
'click .js-submit'(event) {
|
||||
event.preventDefault();
|
||||
this.currentCard.setColor(this.currentColor.get());
|
||||
Popup.close();
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-remove-color'() {
|
||||
'click .js-remove-color'(event) {
|
||||
event.preventDefault();
|
||||
this.currentCard.setColor(null);
|
||||
Popup.close();
|
||||
Popup.back();
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -8,20 +8,6 @@ textarea.js-edit-checklist-item {
|
||||
resize: none;
|
||||
height: 34px;
|
||||
}
|
||||
.card-details .text-show-at-minicard {
|
||||
width: 350px;
|
||||
text-align: left;
|
||||
}
|
||||
.minicard .text-show-at-minicard {
|
||||
display: none;
|
||||
}
|
||||
.text-some-space {
|
||||
width: 20px;
|
||||
}
|
||||
.text-hide-checked-items {
|
||||
width: 400px;
|
||||
text-align: left;
|
||||
}
|
||||
.delete-text,
|
||||
.js-delete-checklist-item,
|
||||
.js-convert-checklist-item-to-card {
|
||||
@@ -40,8 +26,6 @@ textarea.js-edit-checklist-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
|
||||
.checklist-progress-bar-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -61,6 +45,9 @@ textarea.js-edit-checklist-item {
|
||||
border-radius: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
.checklist-title {
|
||||
padding: 10px;
|
||||
}
|
||||
.checklist-title .checkbox {
|
||||
float: left;
|
||||
width: 30px;
|
||||
|
||||
@@ -9,10 +9,19 @@ template(name="checklists")
|
||||
else
|
||||
a.add-checklist-top.js-open-inlined-form(title="{{_ 'add-checklist'}}")
|
||||
i.fa.fa-plus
|
||||
if currentUser.isBoardMember
|
||||
.material-toggle-switch(title="{{_ 'hide-finished-checklist'}}")
|
||||
//span.toggle-switch-title
|
||||
if card.hideFinishedChecklistIfItemsAreHidden
|
||||
input.toggle-switch(type="checkbox" id="toggleHideFinishedChecklist" checked="checked")
|
||||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleHideFinishedChecklist")
|
||||
label.toggle-label(for="toggleHideFinishedChecklist")
|
||||
|
||||
.card-checklist-items
|
||||
each checklist in checklists
|
||||
+checklistDetail(checklist=checklist)
|
||||
if checklist.showChecklist card.hideFinishedChecklistIfItemsAreHidden
|
||||
+checklistDetail(checklist = checklist card = card)
|
||||
|
||||
if canModifyCard
|
||||
+inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId)
|
||||
@@ -22,7 +31,7 @@ template(name="checklists")
|
||||
i.fa.fa-plus
|
||||
|
||||
template(name="checklistDetail")
|
||||
.js-checklist.checklist
|
||||
.js-checklist.checklist.nodragscroll
|
||||
+inlinedForm(classNames="js-edit-checklist-title" checklist = checklist)
|
||||
+editChecklistItemForm(checklist = checklist)
|
||||
else
|
||||
@@ -47,7 +56,7 @@ template(name="checklistDetail")
|
||||
.checklist-progress-text {{finishedPercent}}%
|
||||
.checklist-progress-bar
|
||||
.checklist-progress(style="width:{{finishedPercent}}%")
|
||||
+checklistItems(checklist = checklist)
|
||||
+checklistItems(checklist = checklist card = card)
|
||||
|
||||
template(name="checklistDeletePopup")
|
||||
p {{_ 'confirm-checklist-delete-popup'}}
|
||||
@@ -64,6 +73,12 @@ template(name="addChecklistItemForm")
|
||||
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItem'}}")
|
||||
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItem")
|
||||
label.toggle-label(for="toggleNewlineBecomesNewChecklistItem")
|
||||
| {{_ 'newLineNewItem'}}
|
||||
if $eq position 'top'
|
||||
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItemOriginOrder'}}")
|
||||
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItemOriginOrder")
|
||||
label.toggle-label(for="toggleNewlineBecomesNewChecklistItemOriginOrder")
|
||||
| {{_ 'originOrder'}}
|
||||
|
||||
template(name="editChecklistItemForm")
|
||||
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
|
||||
@@ -87,7 +102,7 @@ template(name="checklistItems")
|
||||
if checklist.items.length
|
||||
if canModifyCard
|
||||
+inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist position="top")
|
||||
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true)
|
||||
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true position="top")
|
||||
else
|
||||
a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}")
|
||||
i.fa.fa-plus
|
||||
@@ -96,7 +111,7 @@ template(name="checklistItems")
|
||||
+inlinedForm(classNames="js-edit-checklist-item" item = item checklist = checklist)
|
||||
+editChecklistItemForm(type = 'item' item = item checklist = checklist)
|
||||
else
|
||||
+checklistItemDetail(item = item checklist = checklist)
|
||||
+checklistItemDetail(item = item checklist = checklist card = card)
|
||||
if canModifyCard
|
||||
+inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist)
|
||||
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true)
|
||||
@@ -105,7 +120,7 @@ template(name="checklistItems")
|
||||
i.fa.fa-plus
|
||||
|
||||
template(name='checklistItemDetail')
|
||||
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if hideCheckedItems}} invisible{{/if}}{{/if}}"
|
||||
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if checklist.hideCheckedChecklistItems}} invisible{{/if}}{{/if}}{{#if checklist.hideAllChecklistItems}} is-checked invisible{{/if}}"
|
||||
role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" tabindex="0")
|
||||
if canModifyCard
|
||||
.check-box-container
|
||||
@@ -122,27 +137,6 @@ template(name='checklistItemDetail')
|
||||
= item.title
|
||||
|
||||
template(name="checklistActionsPopup")
|
||||
if currentUser.isBoardMember
|
||||
span.text-show-at-minicard
|
||||
| {{_ 'show-at-minicard'}}
|
||||
.material-toggle-switch(title="{{_ 'show-checklist-at-minicard'}}")
|
||||
if showAtMinicard
|
||||
input.toggle-switch(type="checkbox" id="toggleShowChecklistAtMinicardButton" checked="checked")
|
||||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleShowChecklistAtMinicardButton")
|
||||
label.toggle-label(for="toggleShowChecklistAtMinicardButton")
|
||||
hr
|
||||
span.text-hide-checked-items
|
||||
| {{_ 'hide-checked-items'}}
|
||||
.material-toggle-switch(title="{{_ 'hide-checked-items'}}")
|
||||
//span.toggle-switch-title
|
||||
//.check-square-icon.i.fa.fa-check-square-o
|
||||
if hideCheckedItems
|
||||
input.toggle-switch(type="checkbox" id="toggleHideCheckedItemsButton" checked="checked")
|
||||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleHideCheckedItemsButton")
|
||||
label.toggle-label(for="toggleHideCheckedItemsButton")
|
||||
hr
|
||||
ul.pop-over-list
|
||||
li
|
||||
a.js-delete-checklist.delete-checklist
|
||||
@@ -154,6 +148,24 @@ template(name="checklistActionsPopup")
|
||||
a.js-copy-checklist.copy-checklist
|
||||
i.fa.fa-copy
|
||||
| {{_ "copyChecklist"}} ...
|
||||
a.js-hide-checked-checklist-items
|
||||
i.fa.fa-eye-slash
|
||||
| {{_ "hideCheckedChecklistItems"}} ...
|
||||
.material-toggle-switch(title="{{_ 'hide-checked-items'}}")
|
||||
if checklist.hideCheckedChecklistItems
|
||||
input.toggle-switch(type="checkbox" id="toggleHideCheckedChecklistItems_{{checklist._id}}" checked="checked")
|
||||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleHideCheckedChecklistItems_{{checklist._id}}")
|
||||
label.toggle-label(for="toggleHideCheckedChecklistItems_{{checklist._id}}")
|
||||
a.js-hide-all-checklist-items
|
||||
i.fa.fa-ban
|
||||
| {{_ "hideAllChecklistItems"}} ...
|
||||
.material-toggle-switch(title="{{_ 'hideAllChecklistItems'}}")
|
||||
if checklist.hideAllChecklistItems
|
||||
input.toggle-switch(type="checkbox" id="toggleHideAllChecklistItems_{{checklist._id}}" checked="checked")
|
||||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleHideAllChecklistItems_{{checklist._id}}")
|
||||
label.toggle-label(for="toggleHideAllChecklistItems_{{checklist._id}}")
|
||||
|
||||
template(name="copyChecklistPopup")
|
||||
+copyAndMoveChecklist
|
||||
|
||||
@@ -119,6 +119,7 @@ BlazeComponent.extendComponent({
|
||||
event.preventDefault();
|
||||
const textarea = this.find('textarea.js-add-checklist-item');
|
||||
const newlineBecomesNewChecklistItem = this.find('input#toggleNewlineBecomesNewChecklistItem');
|
||||
const newlineBecomesNewChecklistItemOriginOrder = this.find('input#toggleNewlineBecomesNewChecklistItemOriginOrder');
|
||||
const title = textarea.value.trim();
|
||||
const checklist = this.currentData().checklist;
|
||||
|
||||
@@ -127,22 +128,28 @@ BlazeComponent.extendComponent({
|
||||
if (newlineBecomesNewChecklistItem.checked) {
|
||||
checklistItems = title.split('\n').map(_value => _value.trim());
|
||||
if (this.currentData().position === 'top') {
|
||||
checklistItems = checklistItems.reverse();
|
||||
if (newlineBecomesNewChecklistItemOriginOrder.checked === false) {
|
||||
checklistItems = checklistItems.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
let addIndex;
|
||||
let sortIndex;
|
||||
if (this.currentData().position === 'top') {
|
||||
sortIndex = Utils.calculateIndexData(null, checklist.firstItem()).base;
|
||||
addIndex = -1;
|
||||
} else {
|
||||
sortIndex = Utils.calculateIndexData(checklist.lastItem(), null).base;
|
||||
addIndex = 1;
|
||||
}
|
||||
for (let checklistItem of checklistItems) {
|
||||
let sortIndex;
|
||||
if (this.currentData().position === 'top') {
|
||||
sortIndex = Utils.calculateIndexData(null, checklist.firstItem()).base;
|
||||
} else {
|
||||
sortIndex = Utils.calculateIndexData(checklist.lastItem(), null).base;
|
||||
}
|
||||
ChecklistItems.insert({
|
||||
title: checklistItem,
|
||||
checklistId: checklist._id,
|
||||
cardId: checklist.cardId,
|
||||
sort: sortIndex,
|
||||
});
|
||||
sortIndex += addIndex;
|
||||
}
|
||||
}
|
||||
// We keep the form opened, empty it.
|
||||
@@ -201,12 +208,8 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
events() {
|
||||
const events = {
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
...events,
|
||||
'click .js-open-checklist-details-menu': Popup.open('checklistActions'),
|
||||
'submit .js-add-checklist': this.addChecklist,
|
||||
'submit .js-edit-checklist-title': this.editChecklist,
|
||||
@@ -217,6 +220,10 @@ BlazeComponent.extendComponent({
|
||||
'focus .js-add-checklist-item': this.focusChecklistItem,
|
||||
// add and delete checklist / checklist-item
|
||||
'click .js-open-inlined-form': this.closeAllInlinedForms,
|
||||
'click #toggleHideFinishedChecklist'(event) {
|
||||
event.preventDefault();
|
||||
this.data().card.toggleHideFinishedChecklist();
|
||||
},
|
||||
keydown: this.pressKey,
|
||||
},
|
||||
];
|
||||
@@ -271,16 +278,6 @@ Template.checklists.helpers({
|
||||
const ret = card.checklists();
|
||||
return ret;
|
||||
},
|
||||
showAtMinicard() {
|
||||
const card = ReactiveCache.getCard(this.cardId);
|
||||
const ret = card.checklists({'showAtMinicard':1});
|
||||
return ret;
|
||||
},
|
||||
hideCheckedItems() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser) return currentUser.hasHideCheckedItems();
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
@@ -303,26 +300,9 @@ BlazeComponent.extendComponent({
|
||||
}).register('addChecklistItemForm');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
toggleItem() {
|
||||
const checklist = this.currentData().checklist;
|
||||
const item = this.currentData().item;
|
||||
if (checklist && item && item._id) {
|
||||
item.toggleItem();
|
||||
}
|
||||
},
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-checklist-item .check-box-container': this.toggleItem,
|
||||
'click #toggleShowChecklistAtMinicardButton'() {
|
||||
const checklist = this.checklist;
|
||||
if (checklist && checklist._id) {
|
||||
Meteor.call('toggleShowChecklistAtMinicard', checklist._id);
|
||||
}
|
||||
},
|
||||
'click #toggleHideCheckedItemsButton'() {
|
||||
Meteor.call('toggleHideCheckedItems');
|
||||
},
|
||||
'click .js-delete-checklist': Popup.afterConfirm('checklistDelete', function () {
|
||||
Popup.back(2);
|
||||
const checklist = this.checklist;
|
||||
@@ -332,6 +312,16 @@ BlazeComponent.extendComponent({
|
||||
}),
|
||||
'click .js-move-checklist': Popup.open('moveChecklist'),
|
||||
'click .js-copy-checklist': Popup.open('copyChecklist'),
|
||||
'click .js-hide-checked-checklist-items'(event) {
|
||||
event.preventDefault();
|
||||
this.data().checklist.toggleHideCheckedChecklistItems();
|
||||
Popup.back();
|
||||
},
|
||||
'click .js-hide-all-checklist-items'(event) {
|
||||
event.preventDefault();
|
||||
this.data().checklist.toggleHideAllChecklistItems();
|
||||
Popup.back();
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -357,11 +347,6 @@ BlazeComponent.extendComponent({
|
||||
}).register('editChecklistItemForm');
|
||||
|
||||
Template.checklistItemDetail.helpers({
|
||||
hideCheckedItems() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
if (user) return user.hasHideCheckedItems();
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
|
||||
@@ -37,5 +37,4 @@ template(name="cardLabelsPopup")
|
||||
= name
|
||||
if(isLabelSelected ../_id)
|
||||
i.card-label-selectable-icon.fa.fa-check
|
||||
if currentUser.isBoardAdmin
|
||||
a.quiet-button.full.js-add-label {{_ 'label-create'}}
|
||||
a.quiet-button.full.js-add-label {{_ 'label-create'}}
|
||||
@@ -1,12 +1,3 @@
|
||||
.minicard .checklists-title,
|
||||
.minicard .add-checklist,
|
||||
.minicard .add-checklist-item,
|
||||
.minicard .checklist-details-menu {
|
||||
display: none;
|
||||
}
|
||||
.minicard .checklist-progress-bar-container {
|
||||
width: 190px; /* TODO: Add adjustable width https://github.com/wekan/wekan/pull/4964 */
|
||||
}
|
||||
.minicard-wrapper {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
@@ -56,10 +47,12 @@
|
||||
float: right;
|
||||
font-size: 18px;
|
||||
padding-right: 30px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
.minicard-details-menu {
|
||||
float: right;
|
||||
font-size: 18px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
@media print {
|
||||
.minicard-details-menu,
|
||||
@@ -99,7 +92,7 @@
|
||||
background-size: contain;
|
||||
height: 145px;
|
||||
user-select: none;
|
||||
margin: -6px -8px 6px -8px;
|
||||
margin: 6px -8px 6px -8px;
|
||||
border-radius: top 2px;
|
||||
}
|
||||
.minicard .minicard-labels {
|
||||
@@ -136,14 +129,6 @@
|
||||
max-width: 100%;
|
||||
margin-right: 4px;
|
||||
}
|
||||
/*
|
||||
.minicard .checklists-title,
|
||||
.minicard .add-checklist,
|
||||
.minicard .add-checklist-item,
|
||||
.minicard .checklist-details-menu {
|
||||
display: none;
|
||||
}
|
||||
*/
|
||||
.minicard .handle {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
@@ -173,7 +158,6 @@
|
||||
.minicard .minicard-title .viewer {
|
||||
display: block;
|
||||
word-wrap: break-word;
|
||||
max-width: 230px;
|
||||
}
|
||||
}
|
||||
.minicard .dates {
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
template(name="minicard")
|
||||
.minicard(
|
||||
.minicard.nodragscroll(
|
||||
class="{{#if isLinkedCard}}linked-card{{/if}}"
|
||||
class="{{#if isLinkedBoard}}linked-board{{/if}}"
|
||||
class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}")
|
||||
if currentUser.isAdmin
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
a.fa.fa-navicon.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
|
||||
.handle
|
||||
.fa.fa-arrows
|
||||
else
|
||||
a.fa.fa-navicon.minicard-details-menu.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
|
||||
if currentUser.isNormal
|
||||
if canModifyCard
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
a.fa.fa-navicon.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
|
||||
.handle
|
||||
@@ -119,12 +112,6 @@ template(name="minicard")
|
||||
+viewer
|
||||
= trueValue
|
||||
|
||||
.card-checklist-attachmentGalleries
|
||||
.card-checklist-attachmentGallery.card-checklists
|
||||
if currentBoard.allowsChecklists
|
||||
//hr
|
||||
//+checklists(cardId=_id showAtMinicard=true)
|
||||
|
||||
if showAssignee
|
||||
if getAssignees
|
||||
.minicard-assignees.js-minicard-assignees
|
||||
@@ -142,7 +129,7 @@ template(name="minicard")
|
||||
+userAvatar(userId=this.userId noRemove=true)
|
||||
|
||||
.badges
|
||||
unless currentUser.isNoComments
|
||||
if canModifyCard
|
||||
if comments.length
|
||||
.badge(title="{{_ 'card-comments-title' comments.length }}")
|
||||
span.badge-icon.fa.fa-comment-o.badge-comment.badge-text
|
||||
@@ -197,12 +184,11 @@ template(name="editCardSortOrderPopup")
|
||||
|
||||
template(name="minicardDetailsActionsPopup")
|
||||
ul.pop-over-list
|
||||
if currentUser.isBoardAdmin
|
||||
if canModifyCard
|
||||
li
|
||||
a.js-move-card
|
||||
i.fa.fa-arrow-right
|
||||
| {{_ 'moveCardPopup-title'}}
|
||||
unless currentUser.isWorker
|
||||
li
|
||||
a.js-copy-card
|
||||
i.fa.fa-copy
|
||||
|
||||
@@ -42,11 +42,7 @@ BlazeComponent.extendComponent({
|
||||
const board = this.data().board();
|
||||
let ret = false;
|
||||
if (board) {
|
||||
ret =
|
||||
board.allowsCreatorOnMinicard === null ||
|
||||
board.allowsCreatorOnMinicard === undefined ||
|
||||
board.allowsCreatorOnMinicard
|
||||
;
|
||||
ret = board.allowsCreatorOnMinicard ?? false;
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
@@ -92,11 +88,6 @@ BlazeComponent.extendComponent({
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .minicard-checklists'() {
|
||||
// Prevents clicking checklist at minicard from opening card details,
|
||||
// while still allowing checking checlist items.
|
||||
event.preventDefault();
|
||||
},
|
||||
'click .js-linked-link'() {
|
||||
if (this.data().isLinkedCard()) Utils.goCardId(this.data().linkedId);
|
||||
else if (this.data().isLinkedBoard())
|
||||
|
||||
@@ -7,18 +7,16 @@
|
||||
border-left: 1px solid #ccc;
|
||||
padding: 0;
|
||||
float: left;
|
||||
min-width: 100px; /* TODO(mark-i-m): hardcoded? */
|
||||
/*max-width: 270px;*/
|
||||
/* Reverted incomplete change list width: */
|
||||
/* https://github.com/wekan/wekan/issues/4558 */
|
||||
/* Orinal width: 270px. Changes not saved yet: */
|
||||
/*resize: both; - List width and height resizeable */
|
||||
/* overflow: auto; - List width and height resizeable */
|
||||
}
|
||||
[id^="swimlane-"] .list:first-child {
|
||||
min-width: 20px;
|
||||
}
|
||||
.list.list-auto-width {
|
||||
flex: 1;
|
||||
}
|
||||
.list:first-child {
|
||||
min-width: 20px;
|
||||
margin-left: 5px;
|
||||
border-left: none;
|
||||
flex: none;
|
||||
}
|
||||
.card-details + .list {
|
||||
border-left: none;
|
||||
@@ -37,6 +35,9 @@
|
||||
box-shadow: none;
|
||||
height: 100px;
|
||||
}
|
||||
.list.list-collapsed {
|
||||
flex: none;
|
||||
}
|
||||
.list.list-composer .open-list-composer,
|
||||
.list .list-composer .open-list-composer {
|
||||
color: #8c8c8c;
|
||||
@@ -48,7 +49,7 @@
|
||||
}
|
||||
.list-header-add {
|
||||
flex: 0 0 auto;
|
||||
padding: 20px 12px 4px;
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
min-height: 20px;
|
||||
}
|
||||
@@ -81,6 +82,20 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.list-rotated {
|
||||
width: 10px;
|
||||
height: 250px;
|
||||
margin-top: -90px;
|
||||
margin-left: -110px;
|
||||
margin-right: 0;
|
||||
transform: rotate(90deg);
|
||||
position: relative;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.list-header .list-rotated {
|
||||
|
||||
}
|
||||
.list-header .list-header-watch-icon {
|
||||
padding-left: 10px;
|
||||
@@ -99,6 +114,23 @@
|
||||
color: #a6a6a6;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.list-header .list-header-collapse-right {
|
||||
color: #a6a6a6;
|
||||
}
|
||||
.list-header .list-header-collapse-left {
|
||||
color: #a6a6a6;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.list-header .list-header-uncollapse-left {
|
||||
color: #a6a6a6;
|
||||
}
|
||||
.list-header .list-header-uncollapse-right {
|
||||
color: #a6a6a6;
|
||||
}
|
||||
.list-header .list-header-collapse {
|
||||
color: #a6a6a6;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.list-header .highlight {
|
||||
color: #ce1414;
|
||||
}
|
||||
@@ -220,11 +252,11 @@
|
||||
padding: 15px 19px;
|
||||
}
|
||||
.list-header {
|
||||
padding: 0 12px 0px;
|
||||
/*Updated padding values for mobile devices, this should fix text grouping issue*/
|
||||
padding: 20px 0px 20px 0px;
|
||||
border-bottom: 0px solid #e4e4e4;
|
||||
height: 60px;
|
||||
min-height: 30px;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.list-header .list-header-left-icon {
|
||||
@@ -297,7 +329,6 @@
|
||||
}
|
||||
.list-header-white {
|
||||
border-bottom: 6px solid #fff;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
.list-header-green {
|
||||
border-bottom: 6px solid #3cb500;
|
||||
@@ -330,7 +361,7 @@
|
||||
border-bottom: 6px solid #51e898;
|
||||
}
|
||||
.list-header-silver {
|
||||
border-bottom: 6px solid unset;
|
||||
border-bottom: 6px solid #e4e4e4;
|
||||
}
|
||||
.list-header-peachpuff {
|
||||
border-bottom: 6px solid #ffdab9;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
template(name='list')
|
||||
.list.js-list(id="js-list-{{_id}}"
|
||||
style="width:{{listWidth}}px;")
|
||||
style="{{#unless collapsed}}min-width:{{listWidth}}px;max-width:{{listConstraint}}px;{{/unless}}"
|
||||
class="{{#if collapsed}}list-collapsed{{/if}} {{#if autoWidth}}list-auto-width{{/if}}")
|
||||
+listHeader
|
||||
+listBody
|
||||
|
||||
|
||||
@@ -196,10 +196,22 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
listWidth() {
|
||||
const user = Meteor.user();
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const list = Template.currentData();
|
||||
return user.getListWidth(list.boardId, list._id);
|
||||
},
|
||||
|
||||
listConstraint() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const list = Template.currentData();
|
||||
return user.getListConstraint(list.boardId, list._id);
|
||||
},
|
||||
|
||||
autoWidth() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const list = Template.currentData();
|
||||
return user.isAutoWidth(list.boardId);
|
||||
},
|
||||
}).register('list');
|
||||
|
||||
Template.miniList.events({
|
||||
|
||||
@@ -1,37 +1,38 @@
|
||||
template(name="listBody")
|
||||
.list-body
|
||||
.minicards.clearfix.js-minicards(class="{{#if reachedWipLimit}}js-list-full{{/if}}")
|
||||
if cards.length
|
||||
+inlinedForm(autoclose=false position="top")
|
||||
+addCardForm(listId=_id position="top")
|
||||
ul.sidebar-list
|
||||
each customFieldsSum
|
||||
li
|
||||
+viewer
|
||||
= name
|
||||
if $eq customFieldsSum.type "number"
|
||||
unless collapsed
|
||||
.list-body(class="{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}")
|
||||
.minicards.clearfix.js-minicards(class="{{#if reachedWipLimit}}js-list-full{{/if}}")
|
||||
if cards.length
|
||||
+inlinedForm(autoclose=false position="top")
|
||||
+addCardForm(listId=_id position="top")
|
||||
ul.sidebar-list
|
||||
each customFieldsSum
|
||||
li
|
||||
+viewer
|
||||
= value
|
||||
if $eq customFieldsSum.type "currency"
|
||||
+viewer
|
||||
= formattedCurrencyCustomFieldValue(value)
|
||||
each (cardsWithLimit (idOrNull ../../_id))
|
||||
a.minicard-wrapper.js-minicard(href=originRelativeUrl
|
||||
class="{{#if cardIsSelected}}is-selected{{/if}}"
|
||||
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
|
||||
if MultiSelection.isActive
|
||||
.materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection(
|
||||
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
|
||||
+minicard(this)
|
||||
if (showSpinner (idOrNull ../../_id))
|
||||
+spinnerList
|
||||
= name
|
||||
if $eq customFieldsSum.type "number"
|
||||
+viewer
|
||||
= value
|
||||
if $eq customFieldsSum.type "currency"
|
||||
+viewer
|
||||
= formattedCurrencyCustomFieldValue(value)
|
||||
each (cardsWithLimit (idOrNull ../../_id))
|
||||
a.minicard-wrapper.js-minicard(href=originRelativeUrl
|
||||
class="{{#if cardIsSelected}}is-selected{{/if}}"
|
||||
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
|
||||
if MultiSelection.isActive
|
||||
.materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection(
|
||||
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
|
||||
+minicard(this)
|
||||
if (showSpinner (idOrNull ../../_id))
|
||||
+spinnerList
|
||||
|
||||
if canSeeAddCard
|
||||
+inlinedForm(autoclose=false position="bottom")
|
||||
+addCardForm(listId=_id position="bottom")
|
||||
else
|
||||
a.open-minicard-composer.js-card-composer.js-open-inlined-form(title="{{_ 'add-card-to-bottom-of-list'}}")
|
||||
i.fa.fa-plus
|
||||
if canSeeAddCard
|
||||
+inlinedForm(autoclose=false position="bottom")
|
||||
+addCardForm(listId=_id position="bottom")
|
||||
else
|
||||
a.open-minicard-composer.js-card-composer.js-open-inlined-form(title="{{_ 'add-card-to-bottom-of-list'}}")
|
||||
i.fa.fa-plus
|
||||
|
||||
template(name="spinnerList")
|
||||
.sk-spinner.sk-spinner-list(
|
||||
|
||||
@@ -231,6 +231,11 @@ BlazeComponent.extendComponent({
|
||||
);
|
||||
},
|
||||
|
||||
isVerticalScrollbars() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
return user && user.isVerticalScrollbars();
|
||||
},
|
||||
|
||||
cardDetailsPopup(event) {
|
||||
if (!Popup.isOpen()) {
|
||||
Popup.open("cardDetails")(event);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
template(name="listHeader")
|
||||
.list-header.js-list-header(
|
||||
.list-header.js-list-header.nodragscroll(
|
||||
class="{{#if limitToShowCardsCount}}list-header-card-count{{/if}}"
|
||||
class=colorClass)
|
||||
+inlinedForm
|
||||
@@ -8,19 +8,40 @@ template(name="listHeader")
|
||||
if isMiniScreen
|
||||
if currentList
|
||||
a.list-header-left-icon.fa.fa-angle-left.js-unselect-list
|
||||
h2.list-header-name(
|
||||
title="{{ moment modifiedAt 'LLL' }}"
|
||||
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
|
||||
+viewer
|
||||
= title
|
||||
if wipLimit.enabled
|
||||
| (
|
||||
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|
||||
|/#{wipLimit.value})
|
||||
|
||||
if showCardsCountForList cards.length
|
||||
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
|
||||
|
||||
else
|
||||
if collapsed
|
||||
a.js-collapse(title="{{_ 'uncollapse'}}")
|
||||
i.fa.fa-arrow-left.list-header-uncollapse-left
|
||||
i.fa.fa-arrow-right.list-header-uncollapse-right
|
||||
if showCardsCountForList cards.length
|
||||
br
|
||||
span.cardCount {{cardsCount}}
|
||||
if isMiniScreen
|
||||
h2.list-header-name(
|
||||
title="{{ moment modifiedAt 'LLL' }}"
|
||||
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
|
||||
+viewer
|
||||
= title
|
||||
if wipLimit.enabled
|
||||
| (
|
||||
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|
||||
|/#{wipLimit.value})
|
||||
if showCardsCountForList cards.length
|
||||
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
|
||||
else
|
||||
div(class="{{#if collapsed}}list-rotated{{/if}}")
|
||||
h2.list-header-name(
|
||||
title="{{ moment modifiedAt 'LLL' }}"
|
||||
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
|
||||
+viewer
|
||||
= title
|
||||
if wipLimit.enabled
|
||||
| (
|
||||
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|
||||
|/#{wipLimit.value})
|
||||
unless collapsed
|
||||
if showCardsCountForList cards.length
|
||||
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
|
||||
if isMiniScreen
|
||||
if currentList
|
||||
if isWatching
|
||||
@@ -36,16 +57,20 @@ template(name="listHeader")
|
||||
else if currentUser.isBoardMember
|
||||
if isWatching
|
||||
i.list-header-watch-icon.fa.fa-eye
|
||||
div.list-header-menu
|
||||
unless currentUser.isCommentOnly
|
||||
//if isBoardAdmin
|
||||
// a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}")
|
||||
if canSeeAddCard
|
||||
a.js-add-card.fa.fa-plus.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}")
|
||||
a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
|
||||
if currentUser.isBoardAdmin
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
a.list-header-handle.handle.fa.fa-arrows.js-list-handle
|
||||
unless collapsed
|
||||
div.list-header-menu
|
||||
unless currentUser.isCommentOnly
|
||||
//if isBoardAdmin
|
||||
// a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}")
|
||||
if canSeeAddCard
|
||||
a.js-add-card.fa.fa-plus.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}")
|
||||
a.js-collapse(title="{{_ 'collapse'}}")
|
||||
i.fa.fa-arrow-right.list-header-collapse-right
|
||||
i.fa.fa-arrow-left.list-header-collapse-left
|
||||
a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
|
||||
if currentUser.isBoardAdmin
|
||||
if isTouchScreenOrShowDesktopDragHandles
|
||||
a.list-header-handle.handle.fa.fa-arrows.js-list-handle
|
||||
|
||||
template(name="editListTitleForm")
|
||||
.list-composer
|
||||
@@ -166,8 +191,14 @@ template(name="setListWidthPopup")
|
||||
label {{_ 'set-list-width-value'}}
|
||||
p
|
||||
input.list-width-value(type="number" value="{{ listWidthValue }}" min="100")
|
||||
input.list-constraint-value(type="number" value="{{ listConstraintValue }}" min="100")
|
||||
input.list-width-apply(type="submit" value="{{_ 'apply'}}")
|
||||
input.list-width-error
|
||||
br
|
||||
a.js-auto-width-board(
|
||||
title="{{#if isAutoWidth}}{{_ 'click-to-disable-auto-width'}}{{else}}{{_ 'click-to-enable-auto-width'}}{{/if}}")
|
||||
i.fa(class="fa-solid fa-{{#if isAutoWidth}}compress{{else}}expand{{/if}}")
|
||||
span {{_ 'auto-list-width'}}
|
||||
|
||||
template(name="listWidthErrorPopup")
|
||||
.list-width-invalid
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import dragscroll from '@wekanteam/dragscroll';
|
||||
|
||||
let listsColors;
|
||||
Meteor.startup(() => {
|
||||
@@ -31,6 +32,17 @@ BlazeComponent.extendComponent({
|
||||
return !status;
|
||||
}
|
||||
},
|
||||
collapsed(check = undefined) {
|
||||
const list = Template.currentData();
|
||||
const status = list.isCollapsed();
|
||||
if (check === undefined) {
|
||||
// just check
|
||||
return status;
|
||||
} else {
|
||||
list.collapse(!status);
|
||||
return !status;
|
||||
}
|
||||
},
|
||||
editTitle(event) {
|
||||
event.preventDefault();
|
||||
const newTitle = this.childComponents('inlinedForm')[0]
|
||||
@@ -104,6 +116,10 @@ BlazeComponent.extendComponent({
|
||||
event.preventDefault();
|
||||
this.starred(!this.starred());
|
||||
},
|
||||
'click .js-collapse'(event) {
|
||||
event.preventDefault();
|
||||
this.collapsed(!this.collapsed());
|
||||
},
|
||||
'click .js-open-list-menu': Popup.open('listAction'),
|
||||
'click .js-add-card.list-header-plus-top'(event) {
|
||||
const listDom = $(event.target).parents(
|
||||
@@ -140,7 +156,7 @@ Template.listActionPopup.helpers({
|
||||
|
||||
isWatching() {
|
||||
return this.findWatcher(Meteor.userId());
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
Template.listActionPopup.events({
|
||||
@@ -332,14 +348,20 @@ BlazeComponent.extendComponent({
|
||||
.val(),
|
||||
10,
|
||||
);
|
||||
const constraint = parseInt(
|
||||
Template.instance()
|
||||
.$('.list-constraint-value')
|
||||
.val(),
|
||||
10,
|
||||
);
|
||||
|
||||
// FIXME(mark-i-m): where do we put constants?
|
||||
if (width < 100 || !width) {
|
||||
if (width < 100 || !width || constraint < 100 || !constraint) {
|
||||
Template.instance()
|
||||
.$('.list-width-error')
|
||||
.click();
|
||||
} else {
|
||||
Meteor.call('applyListWidth', board, list._id, width);
|
||||
Meteor.call('applyListWidth', board, list._id, width, constraint);
|
||||
Popup.back();
|
||||
}
|
||||
},
|
||||
@@ -347,12 +369,28 @@ BlazeComponent.extendComponent({
|
||||
listWidthValue() {
|
||||
const list = Template.currentData();
|
||||
const board = list.boardId;
|
||||
return Meteor.user().getListWidth(board, list._id);
|
||||
return ReactiveCache.getCurrentUser().getListWidth(board, list._id);
|
||||
},
|
||||
|
||||
listConstraintValue() {
|
||||
const list = Template.currentData();
|
||||
const board = list.boardId;
|
||||
return ReactiveCache.getCurrentUser().getListConstraint(board, list._id);
|
||||
},
|
||||
|
||||
isAutoWidth() {
|
||||
const boardId = Utils.getCurrentBoardId();
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
return user && user.isAutoWidth(boardId);
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-auto-width-board'() {
|
||||
dragscroll.reset();
|
||||
ReactiveCache.getCurrentUser().toggleAutoWidth(Utils.getCurrentBoardId());
|
||||
},
|
||||
'click .list-width-apply': this.applyListWidth,
|
||||
'click .list-width-error': Popup.open('listWidthError'),
|
||||
},
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
.my-cards-board-wrapper {
|
||||
border-radius: 0 0 4px 4px;
|
||||
min-width: 400px;
|
||||
margin-bottom: 2rem;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-color: #a2a2a2;
|
||||
}
|
||||
.my-cards-board-title {
|
||||
font-size: 1.4rem;
|
||||
font-weight: bold;
|
||||
padding: 0.5rem;
|
||||
background-color: #808080;
|
||||
color: #fff;
|
||||
}
|
||||
.my-cards-swimlane-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
padding: 0.5rem;
|
||||
padding-bottom: 0.4rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
.swimlane-default-color {
|
||||
background-color: #d3d3d3;
|
||||
}
|
||||
.my-cards-list-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
text-align: center;
|
||||
margin-bottom: 0.7rem;
|
||||
}
|
||||
.my-cards-list-wrapper {
|
||||
margin: 1rem;
|
||||
border-radius: 5px;
|
||||
display: inline-grid;
|
||||
min-width: 250px;
|
||||
max-width: 350px;
|
||||
}
|
||||
.my-cards-card-wrapper {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.my-cards-dueat-list-wrapper {
|
||||
max-width: 500px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
.my-cards-board-table thead {
|
||||
border-bottom: 3px solid #4d4d4d;
|
||||
background-color: transparent;
|
||||
}
|
||||
.my-cards-board-table th,
|
||||
.my-cards-board-table td {
|
||||
border: 0;
|
||||
}
|
||||
.my-cards-board-table tr {
|
||||
border-bottom: 2px solid #a2a2a2;
|
||||
}
|
||||
.my-cards-card-title-table {
|
||||
font-weight: bold;
|
||||
padding-left: 2px;
|
||||
max-width: 243px;
|
||||
}
|
||||
.my-cards-board-badge {
|
||||
width: 36px;
|
||||
height: 24px;
|
||||
float: left;
|
||||
border-radius: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
template(name="accessibilityHeaderBar")
|
||||
if currentUser
|
||||
h1
|
||||
| {{_ 'accessibility-title'}}
|
||||
|
||||
template(name="accessibility")
|
||||
if currentUser
|
||||
| {{_ 'accessibility-content'}}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ReactiveCache } from '/imports/reactiveCache';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.error = new ReactiveVar('');
|
||||
this.loading = new ReactiveVar(false);
|
||||
|
||||
Meteor.subscribe('setting');
|
||||
},
|
||||
}).register('accessibility');
|
||||
@@ -2,15 +2,15 @@
|
||||
.inlined-form a.fa.fa-brands.fa-markdown {
|
||||
float: right;
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
top: -10px;
|
||||
right: 60px;
|
||||
}
|
||||
.new-comment a.fa.fa-copy,
|
||||
.inlined-form a.fa.fa-copy {
|
||||
float: right;
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
right: 6px;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
right: 5px;
|
||||
}
|
||||
.js-inlined-form.viewer.btn-sm {
|
||||
position: absolute;
|
||||
|
||||
@@ -446,6 +446,12 @@ a:not(.disabled).is-active i.fa {
|
||||
padding: 0;
|
||||
padding-top: 15px;
|
||||
}
|
||||
.no-scrollbars {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.no-scrollbars::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
}
|
||||
@media screen and (max-width: 800px) {
|
||||
#content {
|
||||
margin: 1px 0px 0px 0px;
|
||||
@@ -470,6 +476,7 @@ a:not(.disabled).is-active i.fa {
|
||||
.select-authentication {
|
||||
width: 100%;
|
||||
}
|
||||
.textBelowCustomLoginLogo,
|
||||
.auth-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -37,10 +37,13 @@ template(name="userFormsLayout")
|
||||
else
|
||||
img(src="{{pathFor '/wekan-logo.svg'}}" alt="" width="300" height="auto")
|
||||
br
|
||||
if currentSetting.textBelowCustomLoginLogo
|
||||
+viewer
|
||||
| {{currentSetting.textBelowCustomLoginLogo}}
|
||||
br
|
||||
if currentSetting.textBelowCustomLoginLogo
|
||||
hr
|
||||
section.textBelowCustomLoginLogo
|
||||
+viewer
|
||||
| {{currentSetting.textBelowCustomLoginLogo}}
|
||||
hr
|
||||
section.auth-layout
|
||||
section.auth-dialog
|
||||
if isLoading
|
||||
+loader
|
||||
|
||||
@@ -58,18 +58,20 @@ template(name="rulesReport")
|
||||
h1 {{_ 'rulesReportTitle'}}
|
||||
if resultsCount
|
||||
table
|
||||
tr
|
||||
th Rule Title
|
||||
th Board Title
|
||||
th actionType
|
||||
th activityType
|
||||
thead
|
||||
tr
|
||||
th Rule Title
|
||||
th Board Title
|
||||
th actionType
|
||||
th activityType
|
||||
|
||||
each rule in results
|
||||
tr
|
||||
td {{ rule.title }}
|
||||
td {{ rule.boardTitle }}
|
||||
td {{ rule.action.actionType }}
|
||||
td {{ rule.trigger.activityType }}
|
||||
tbody
|
||||
tr
|
||||
td {{ rule.title }}
|
||||
td {{ rule.boardTitle }}
|
||||
td {{ rule.action.actionType }}
|
||||
td {{ rule.trigger.activityType }}
|
||||
else
|
||||
div {{_ 'no-results' }}
|
||||
|
||||
@@ -77,22 +79,24 @@ template(name="filesReport")
|
||||
h1 {{_ 'filesReportTitle'}}
|
||||
if resultsCount
|
||||
table
|
||||
tr
|
||||
th Filename
|
||||
th.right Size (kB)
|
||||
th MIME Type
|
||||
th Attachment ID
|
||||
th Board ID
|
||||
th Card ID
|
||||
thead
|
||||
tr
|
||||
th Filename
|
||||
th.right Size (kB)
|
||||
th MIME Type
|
||||
th Attachment ID
|
||||
th Board ID
|
||||
th Card ID
|
||||
|
||||
each att in results
|
||||
tr
|
||||
td {{ att.name }}
|
||||
td.right {{ fileSize att.size }}
|
||||
td {{ att.type }}
|
||||
td {{ att._id }}
|
||||
td {{ att.meta.boardId }}
|
||||
td {{ att.meta.cardId }}
|
||||
tbody
|
||||
tr
|
||||
td {{ att.name }}
|
||||
td.right {{ fileSize att.size }}
|
||||
td {{ att.type }}
|
||||
td {{ att._id }}
|
||||
td {{ att.meta.boardId }}
|
||||
td {{ att.meta.cardId }}
|
||||
else
|
||||
div {{_ 'no-results' }}
|
||||
|
||||
@@ -100,22 +104,24 @@ template(name="cardsReport")
|
||||
h1 {{_ 'cardsReportTitle'}}
|
||||
if resultsCount
|
||||
table.table
|
||||
tr
|
||||
th Card Title
|
||||
th Board
|
||||
th Swimlane
|
||||
th List
|
||||
th Members
|
||||
th Assignees
|
||||
thead
|
||||
tr
|
||||
th Card Title
|
||||
th Board
|
||||
th Swimlane
|
||||
th List
|
||||
th Members
|
||||
th Assignees
|
||||
|
||||
each card in results
|
||||
tr
|
||||
td {{abbreviate card.title }}
|
||||
td {{abbreviate card.board.title }}
|
||||
td {{abbreviate card.swimlane.title }}
|
||||
td {{abbreviate card.list.title }}
|
||||
td {{userNames card.members }}
|
||||
td {{userNames card.assignees }}
|
||||
tbody
|
||||
tr
|
||||
td {{abbreviate card.title }}
|
||||
td {{abbreviate card.board.title }}
|
||||
td {{abbreviate card.swimlane.title }}
|
||||
td {{abbreviate card.list.title }}
|
||||
td {{userNames card.members }}
|
||||
td {{userNames card.assignees }}
|
||||
else
|
||||
div {{_ 'no-results' }}
|
||||
|
||||
@@ -123,22 +129,25 @@ template(name="boardsReport")
|
||||
h1 {{_ 'boardsReportTitle'}}
|
||||
if resultsCount
|
||||
table.table
|
||||
tr
|
||||
th Title
|
||||
th Id
|
||||
th Permission
|
||||
th Archived?
|
||||
th Members
|
||||
th Organizations
|
||||
th Teams
|
||||
|
||||
each board in results
|
||||
thead
|
||||
tr
|
||||
td {{abbreviate board.title }}
|
||||
td {{abbreviate board._id }}
|
||||
td {{ board.permission }}
|
||||
td
|
||||
= yesOrNo(board.archived)
|
||||
td {{userNames board.members }}
|
||||
th Title
|
||||
th Id
|
||||
th Permission
|
||||
th Archived?
|
||||
th Members
|
||||
th Organizations
|
||||
th Teams
|
||||
each board in results
|
||||
tbody
|
||||
tr
|
||||
td {{abbreviate board.title }}
|
||||
td {{abbreviate board._id }}
|
||||
td {{ board.permission }}
|
||||
td
|
||||
= yesOrNo(board.archived)
|
||||
td {{userNames board.members }}
|
||||
td {{orgs board.orgs }}
|
||||
td {{teams board.teams }}
|
||||
else
|
||||
div {{_ 'no-results' }}
|
||||
@@ -170,8 +170,27 @@ class AdminReport extends BlazeComponent {
|
||||
.join(", ");
|
||||
return ret;
|
||||
}
|
||||
teams(memberTeams) {
|
||||
const ret = (memberTeams || [])
|
||||
.map(_memberTeam => {
|
||||
const _ret = ReactiveCache.getTeam(_memberTeam.teamId)?.teamDisplayName || _memberTeam.teamId;
|
||||
return _ret;
|
||||
})
|
||||
.join(", ");
|
||||
return ret;
|
||||
}
|
||||
orgs(orgs) {
|
||||
const ret = (orgs || [])
|
||||
.map(_orgs => {
|
||||
const _ret = ReactiveCache.getOrg(_orgs.orgId)?.orgDisplayName || _orgs.orgId;
|
||||
return _ret;
|
||||
})
|
||||
.join(", ");
|
||||
return ret;
|
||||
}
|
||||
}.register('boardsReport'));
|
||||
|
||||
|
||||
(class extends AdminReport {
|
||||
collection = Cards;
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ template(name="people")
|
||||
|
||||
template(name="orgGeneral")
|
||||
table
|
||||
tbody
|
||||
thead
|
||||
tr
|
||||
th {{_ 'displayName'}}
|
||||
th {{_ 'description'}}
|
||||
@@ -84,12 +84,14 @@ template(name="orgGeneral")
|
||||
th {{_ 'active'}}
|
||||
th
|
||||
+newOrgRow
|
||||
tbody
|
||||
tr
|
||||
each org in orgList
|
||||
+orgRow(orgId=org._id)
|
||||
|
||||
template(name="teamGeneral")
|
||||
table
|
||||
tbody
|
||||
thead
|
||||
tr
|
||||
th {{_ 'displayName'}}
|
||||
th {{_ 'description'}}
|
||||
@@ -99,6 +101,8 @@ template(name="teamGeneral")
|
||||
th {{_ 'active'}}
|
||||
th
|
||||
+newTeamRow
|
||||
tbody
|
||||
tr
|
||||
each team in teamList
|
||||
+teamRow(teamId=team._id)
|
||||
|
||||
@@ -106,7 +110,7 @@ template(name="peopleGeneral")
|
||||
#divAddOrRemoveTeamContainer
|
||||
+modifyTeamsUsers
|
||||
table
|
||||
tbody
|
||||
thead
|
||||
tr
|
||||
th
|
||||
+selectAllUser
|
||||
@@ -124,6 +128,8 @@ template(name="peopleGeneral")
|
||||
th {{_ 'teams'}}
|
||||
th
|
||||
+newUserRow
|
||||
tbody
|
||||
tr
|
||||
each user in peopleList
|
||||
+peopleRow(userId=user._id)
|
||||
|
||||
@@ -494,9 +500,9 @@ template(name="modifyTeamsUsers")
|
||||
| {{_ 'r-action'}}
|
||||
.form-group.flex
|
||||
input.wekan-form-control#addAction(type="radio" name="action" value="true" checked="checked")
|
||||
span {{_ 'add'}}
|
||||
label(for=addAction) {{_ 'add'}}
|
||||
input.wekan-form-control#deleteAction(type="radio" name="action" value="false")
|
||||
span {{_ 'delete'}}
|
||||
label(for=deleteAction) {{_ 'delete'}}
|
||||
div.buttonsContainer
|
||||
input.primary.wide#addTeamBtn(type="submit" value="{{_ 'save'}}")
|
||||
input.primary.wide#cancelBtn(type="submit" value="{{_ 'cancel'}}")
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
display: -moz-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
.setting-content {
|
||||
color: #727479;
|
||||
background: #dedede;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
.setting-content .content-title {
|
||||
@@ -56,6 +58,8 @@
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
.setting-content .content-body .main-body ul li {
|
||||
padding: 0.5rem 0.5rem;
|
||||
@@ -68,26 +72,31 @@
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
.setting-content .content-body .main-body ul li .admin-announcement,
|
||||
.setting-content .content-body .main-body ul li .admin-accessibility,
|
||||
.setting-content .content-body .main-body ul li .invite-people,
|
||||
.setting-content .content-body .main-body ul li .layout {
|
||||
padding-left: 20px;
|
||||
}
|
||||
.setting-content .content-body .main-body ul li .admin-announcement li,
|
||||
.setting-content .content-body .main-body ul li .admin-accessibility li,
|
||||
.setting-content .content-body .main-body ul li .invite-people li,
|
||||
.setting-content .content-body .main-body ul li .layout li {
|
||||
min-width: 500px;
|
||||
}
|
||||
.setting-content .content-body .main-body ul li .admin-announcement li ul.no-margin-bottom,
|
||||
.setting-content .content-body .main-body ul li .admin-accessibility li ul.no-margin-bottom,
|
||||
.setting-content .content-body .main-body ul li .invite-people li ul.no-margin-bottom,
|
||||
.setting-content .content-body .main-body ul li .layout li ul.no-margin-bottom {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.setting-content .content-body .main-body ul li .admin-announcement li .bg-white a,
|
||||
.setting-content .content-body .main-body ul li .admin-accessibility li .bg-white a,
|
||||
.setting-content .content-body .main-body ul li .invite-people li .bg-white a,
|
||||
.setting-content .content-body .main-body ul li .layout li .bg-white a {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
.setting-content .content-body .main-body ul li .admin-announcement li .bg-white a.is-checked,
|
||||
.setting-content .content-body .main-body ul li .admin-accessibility li .bg-white a.is-checked,
|
||||
.setting-content .content-body .main-body ul li .invite-people li .bg-white a.is-checked,
|
||||
.setting-content .content-body .main-body ul li .layout li .bg-white a.is-checked {
|
||||
background: #fff;
|
||||
|
||||
@@ -30,6 +30,10 @@ template(name="setting")
|
||||
a.js-setting-menu(data-id="announcement-setting")
|
||||
i.fa.fa-bullhorn
|
||||
| {{_ 'admin-announcement'}}
|
||||
//li
|
||||
// a.js-setting-menu(data-id="accessibility-setting")
|
||||
// i.fa.fa-universal-access
|
||||
// | {{_ 'accessibility'}}
|
||||
li
|
||||
a.js-setting-menu(data-id="layout-setting")
|
||||
i.fa.fa-object-group
|
||||
@@ -52,6 +56,8 @@ template(name="setting")
|
||||
+tableVisibilityModeSettings
|
||||
else if announcementSetting.get
|
||||
+announcementSettings
|
||||
else if accessibilitySetting.get
|
||||
+accessibilitySettings
|
||||
else if layoutSetting.get
|
||||
+layoutSettings
|
||||
else if webhookSetting.get
|
||||
@@ -137,34 +143,32 @@ template(name='tableVisibilityModeSettings')
|
||||
.title {{_ 'tableVisibilityMode-allowPrivateOnly'}}
|
||||
.form-group.flex
|
||||
input.wekan-form-control#accounts-allowPrivateOnly(type="radio" name="allowPrivateOnly" value="true" checked="{{#if allowPrivateOnly}}checked{{/if}}")
|
||||
span {{_ 'yes'}}
|
||||
label {{_ 'yes'}}
|
||||
input.wekan-form-control#accounts-allowPrivateOnly(type="radio" name="allowPrivateOnly" value="false" checked="{{#unless allowPrivateOnly}}checked{{/unless}}")
|
||||
span {{_ 'no'}}
|
||||
label {{_ 'no'}}
|
||||
button.js-tableVisibilityMode-save.primary {{_ 'save'}}
|
||||
|
||||
template(name='accountSettings')
|
||||
ul#account-setting.setting-detail
|
||||
li
|
||||
button.js-all-hide-system-messages.primary {{_ 'hide-system-messages-of-all-users'}}
|
||||
li.accounts-form
|
||||
.title {{_ 'accounts-allowEmailChange'}}
|
||||
.form-group.flex
|
||||
input.wekan-form-control#accounts-allowEmailChange(type="radio" name="allowEmailChange" value="true" checked="{{#if allowEmailChange}}checked{{/if}}")
|
||||
span {{_ 'yes'}}
|
||||
label {{_ 'yes'}}
|
||||
input.wekan-form-control#accounts-allowEmailChange(type="radio" name="allowEmailChange" value="false" checked="{{#unless allowEmailChange}}checked{{/unless}}")
|
||||
span {{_ 'no'}}
|
||||
label {{_ 'no'}}
|
||||
.title {{_ 'accounts-allowUserNameChange'}}
|
||||
.form-group.flex
|
||||
input.wekan-form-control#accounts-allowUserNameChange(type="radio" name="allowUserNameChange" value="true" checked="{{#if allowUserNameChange}}checked{{/if}}")
|
||||
span {{_ 'yes'}}
|
||||
label {{_ 'yes'}}
|
||||
input.wekan-form-control#accounts-allowUserNameChange(type="radio" name="allowUserNameChange" value="false" checked="{{#unless allowUserNameChange}}checked{{/unless}}")
|
||||
span {{_ 'no'}}
|
||||
label {{_ 'no'}}
|
||||
.title {{_ 'accounts-allowUserDelete'}}
|
||||
.form-group.flex
|
||||
input.wekan-form-control#accounts-allowUserDelete(type="radio" name="allowUserDelete" value="true" checked="{{#if allowUserDelete}}checked{{/if}}")
|
||||
span {{_ 'yes'}}
|
||||
label {{_ 'yes'}}
|
||||
input.wekan-form-control#accounts-allowUserDelete(type="radio" name="allowUserDelete" value="false" checked="{{#unless allowUserDelete}}checked{{/unless}}")
|
||||
span {{_ 'no'}}
|
||||
label {{_ 'no'}}
|
||||
button.js-accounts-save.primary {{_ 'save'}}
|
||||
|
||||
template(name='announcementSettings')
|
||||
@@ -183,8 +187,33 @@ template(name='announcementSettings')
|
||||
li
|
||||
button.js-announcement-save.primary {{_ 'save'}}
|
||||
|
||||
template(name='accessibilitySettings')
|
||||
ul#accessibility-setting.setting-detail
|
||||
li
|
||||
a.flex.js-toggle-accessibility
|
||||
.materialCheckBox(class="{{#if currentAccessibility.enabled}}is-checked{{/if}}")
|
||||
|
||||
span {{_ 'admin-accessibility-active'}}
|
||||
li
|
||||
.title {{_ 'accessibility-title'}}
|
||||
.form-group
|
||||
input.wekan-form-control#accessibility-title(type="text", placeholder="" value="{{currentSetting.accessibilityTitle}}")
|
||||
li
|
||||
.accessibility-content(class="{{#if currentAccessibility.enabled}}{{else}}hide{{/if}}")
|
||||
ul
|
||||
li
|
||||
.title {{_ 'admin-accessibility-title'}}
|
||||
textarea#admin-accessibility.wekan-form-control= currentAccessibility.accessibilityTitle
|
||||
li
|
||||
.title {{_ 'admin-accessibility-content'}}
|
||||
textarea#admin-accessibility.wekan-form-control= currentAccessibility.accessibilityContent
|
||||
li
|
||||
button.js-accessibility-save.primary {{_ 'save'}}
|
||||
|
||||
template(name='layoutSettings')
|
||||
ul#layout-setting.setting-detail
|
||||
li
|
||||
button.js-all-boards-hide-activities.primary {{_ 'hide-activities-of-all-boards'}}
|
||||
li.layout-form
|
||||
.title {{_ 'oidc-button-text'}}
|
||||
.form-group
|
||||
@@ -201,9 +230,9 @@ template(name='layoutSettings')
|
||||
.title {{_ 'display-authentication-method'}}
|
||||
.form-group.flex
|
||||
input.wekan-form-control#display-authentication-method(type="radio" name="displayAuthenticationMethod" value="true" checked="{{#if currentSetting.displayAuthenticationMethod}}checked{{/if}}")
|
||||
span {{_ 'yes'}}
|
||||
label {{_ 'yes'}}
|
||||
input.wekan-form-control#display-authentication-method(type="radio" name="displayAuthenticationMethod" value="false" checked="{{#unless currentSetting.displayAuthenticationMethod}}checked{{/unless}}")
|
||||
span {{_ 'no'}}
|
||||
label {{_ 'no'}}
|
||||
li.layout-form
|
||||
.title {{_ 'default-authentication-method'}}
|
||||
+selectAuthenticationMethod(authenticationMethod=currentSetting.defaultAuthenticationMethod)
|
||||
@@ -218,9 +247,9 @@ template(name='layoutSettings')
|
||||
.title {{_ 'hide-logo'}}
|
||||
.form-group.flex
|
||||
input.wekan-form-control#hide-logo(type="radio" name="hideLogo" value="true" checked="{{#if currentSetting.hideLogo}}checked{{/if}}")
|
||||
span {{_ 'yes'}}
|
||||
label {{_ 'yes'}}
|
||||
input.wekan-form-control#hide-logo(type="radio" name="hideLogo" value="false" checked="{{#unless currentSetting.hideLogo}}checked{{/unless}}")
|
||||
span {{_ 'no'}}
|
||||
label {{_ 'no'}}
|
||||
li.layout-form
|
||||
.title {{_ 'custom-login-logo-image-url'}}
|
||||
.form-group
|
||||
@@ -257,16 +286,16 @@ template(name='layoutSettings')
|
||||
.title {{_ 'hide-card-counter-list'}}
|
||||
.form-group.flex
|
||||
input.wekan-form-control#hide-card-counter-list(type="radio" name="hideCardCounterList" value="true" checked="{{#if currentSetting.hideCardCounterList}}checked{{/if}}")
|
||||
span {{_ 'yes'}}
|
||||
label {{_ 'yes'}}
|
||||
input.wekan-form-control#hide-card-counter-list(type="radio" name="hideCardCounterList" value="false" checked="{{#unless currentSetting.hideCardCounterList}}checked{{/unless}}")
|
||||
span {{_ 'no'}}
|
||||
label {{_ 'no'}}
|
||||
li.layout-form
|
||||
.title {{_ 'hide-board-member-list'}}
|
||||
.form-group.flex
|
||||
input.wekan-form-control#hide-board-member-list(type="radio" name="hideBoardMemberList" value="true" checked="{{#if currentSetting.hideBoardMemberList}}checked{{/if}}")
|
||||
span {{_ 'yes'}}
|
||||
label {{_ 'yes'}}
|
||||
input.wekan-form-control#hide-board-member-list(type="radio" name="hideBoardMemberList" value="false" checked="{{#unless currentSetting.hideBoardMemberList}}checked{{/unless}}")
|
||||
span {{_ 'no'}}
|
||||
label {{_ 'no'}}
|
||||
li
|
||||
button.js-save-layout.primary {{_ 'save'}}
|
||||
|
||||
|
||||
@@ -89,6 +89,9 @@ BlazeComponent.extendComponent({
|
||||
toggleHideBoardMemberList() {
|
||||
$('#hide-board-member-list').toggleClass('is-checked');
|
||||
},
|
||||
toggleAccessibilityPageEnabled() {
|
||||
$('#accessibility-page-enabled').toggleClass('is-checked');
|
||||
},
|
||||
toggleDisplayAuthenticationMethod() {
|
||||
$('#display-authentication-method').toggleClass('is-checked');
|
||||
},
|
||||
@@ -239,7 +242,15 @@ BlazeComponent.extendComponent({
|
||||
const displayAuthenticationMethod =
|
||||
$('input[name=displayAuthenticationMethod]:checked').val() === 'true';
|
||||
const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val();
|
||||
|
||||
/*
|
||||
const accessibilityPageEnabled = $('input[name=accessibilityPageEnabled]:checked').val() === 'true';
|
||||
const accessibilityTitle = $('#accessibility-title')
|
||||
.val()
|
||||
.trim();
|
||||
const accessibilityContent = $('#accessibility-content')
|
||||
.val()
|
||||
.trim();
|
||||
*/
|
||||
const spinnerName = $('#spinnerName').val();
|
||||
|
||||
try {
|
||||
@@ -265,6 +276,11 @@ BlazeComponent.extendComponent({
|
||||
legalNotice,
|
||||
},
|
||||
});
|
||||
/*
|
||||
accessibilityPageEnabled,
|
||||
accessibilityTitle,
|
||||
accessibilityContent,
|
||||
*/
|
||||
} catch (e) {
|
||||
return;
|
||||
} finally {
|
||||
@@ -301,6 +317,7 @@ BlazeComponent.extendComponent({
|
||||
'click a.js-toggle-hide-logo': this.toggleHideLogo,
|
||||
'click a.js-toggle-hide-card-counter-list': this.toggleHideCardCounterList,
|
||||
'click a.js-toggle-hide-board-member-list': this.toggleHideBoardMemberList,
|
||||
'click a.js-toggle-accessibility-page-enabled': this.toggleAccessibilityPageEnabled,
|
||||
'click button.js-save-layout': this.saveLayout,
|
||||
'click a.js-toggle-display-authentication-method': this
|
||||
.toggleDisplayAuthenticationMethod,
|
||||
@@ -336,12 +353,12 @@ BlazeComponent.extendComponent({
|
||||
allowUserDelete() {
|
||||
return AccountSettings.findOne('accounts-allowUserDelete').booleanValue;
|
||||
},
|
||||
allHideSystemMessages() {
|
||||
Meteor.call('setAllUsersHideSystemMessages', (err, ret) => {
|
||||
allBoardsHideActivities() {
|
||||
Meteor.call('setAllBoardsHideActivities', (err, ret) => {
|
||||
if (!err && ret) {
|
||||
if (ret === true) {
|
||||
const message = `${TAPi18n.__(
|
||||
'now-system-messages-of-all-users-are-hidden',
|
||||
'now-activities-of-all-boards-are-hidden',
|
||||
)}`;
|
||||
alert(message);
|
||||
}
|
||||
@@ -359,7 +376,7 @@ BlazeComponent.extendComponent({
|
||||
'click button.js-accounts-save': this.saveAccountsChange,
|
||||
},
|
||||
{
|
||||
'click button.js-all-hide-system-messages': this.allHideSystemMessages,
|
||||
'click button.js-all-boards-hide-activities': this.allBoardsHideActivities,
|
||||
},
|
||||
];
|
||||
},
|
||||
@@ -376,12 +393,12 @@ BlazeComponent.extendComponent({
|
||||
allowPrivateOnly() {
|
||||
return TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
|
||||
},
|
||||
allHideSystemMessages() {
|
||||
Meteor.call('setAllUsersHideSystemMessages', (err, ret) => {
|
||||
allBoardsHideActivities() {
|
||||
Meteor.call('setAllBoardsHideActivities', (err, ret) => {
|
||||
if (!err && ret) {
|
||||
if (ret === true) {
|
||||
const message = `${TAPi18n.__(
|
||||
'now-system-messages-of-all-users-are-hidden',
|
||||
'now-activities-of-all-boards-are-hidden',
|
||||
)}`;
|
||||
alert(message);
|
||||
}
|
||||
@@ -399,7 +416,7 @@ BlazeComponent.extendComponent({
|
||||
'click button.js-tableVisibilityMode-save': this.saveTableVisibilityChange,
|
||||
},
|
||||
{
|
||||
'click button.js-all-hide-system-messages': this.allHideSystemMessages,
|
||||
'click button.js-all-boards-hide-activities': this.allBoardsHideActivities,
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
@@ -34,13 +34,14 @@ template(name="translation")
|
||||
|
||||
template(name="translationGeneral")
|
||||
table
|
||||
tbody
|
||||
thead
|
||||
tr
|
||||
th {{_ 'language'}}
|
||||
th {{_ 'text'}}
|
||||
th {{_ 'translation-text'}}
|
||||
th
|
||||
+newTranslationRow
|
||||
tbody
|
||||
each translation in translationList
|
||||
+translationRow(translationId=translation._id)
|
||||
|
||||
|
||||
@@ -3,34 +3,31 @@
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.sidebar .sidebar-shadow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
.sidebar {
|
||||
background: #f7f7f7;
|
||||
box-shadow: -10px 0px 5px -10px #b3b3b3;
|
||||
z-index: 10;
|
||||
}
|
||||
.sidebar-xmark {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
font-size: 25px;
|
||||
padding: 10px;
|
||||
}
|
||||
.sidebar-xmark:hover {
|
||||
background: rgba(0,0,0,0.15);
|
||||
}
|
||||
.sidebar-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 10px 0px 10px;
|
||||
}
|
||||
.sidebar .sidebar-content {
|
||||
padding: 12px;
|
||||
margin-bottom: 1.6em;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
width: 90%;
|
||||
padding: 0 12px;
|
||||
}
|
||||
.sidebar .sidebar-content .hide-btn {
|
||||
display: none;
|
||||
@@ -106,21 +103,23 @@
|
||||
margin-right: 10px;
|
||||
}
|
||||
.sidebar .sidebar-shortcuts {
|
||||
position: absolute;
|
||||
margin-left: 40%;
|
||||
padding: 0;
|
||||
top: 7px;
|
||||
font-size: 0.8em;
|
||||
font-size: 1em;
|
||||
line-height: 1.6em;
|
||||
color: #999;
|
||||
}
|
||||
.sidebar .sidebar-shortcuts .sidebar-btn {
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.board-sidebar {
|
||||
width: 548px;
|
||||
right: -548px;
|
||||
display: none;
|
||||
width: 30vw;
|
||||
z-index: 100;
|
||||
transition: top 0.1s, right 0.1s, width 0.1s;
|
||||
}
|
||||
.board-sidebar.is-open {
|
||||
right: 0;
|
||||
display: block;
|
||||
}
|
||||
.board-widget h4 {
|
||||
margin: 5px 0;
|
||||
|
||||
@@ -1,36 +1,56 @@
|
||||
template(name="sidebar")
|
||||
.board-sidebar.sidebar(class="{{#if isOpen}}is-open{{/if}}")
|
||||
.board-sidebar.sidebar(class="{{#if isOpen}}is-open{{/if}} {{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}")
|
||||
//a.sidebar-tongue.js-toggle-sidebar(
|
||||
// class="{{#if isTongueHidden}}is-hidden{{/if}}",
|
||||
// title="{{showTongueTitle}}")
|
||||
// i.fa.fa-navicon
|
||||
.sidebar-shadow
|
||||
.sidebar-actions
|
||||
.sidebar-shortcuts
|
||||
a.sidebar-btn.js-shortcuts(title="{{_ 'keyboard-shortcuts' }}")
|
||||
i.fa.fa-keyboard-o
|
||||
span {{_ 'keyboard-shortcuts' }}
|
||||
a.sidebar-btn.js-keyboard-shortcuts-toggle(
|
||||
title="{{#if isKeyboardShortcuts}}{{_ 'keyboard-shortcuts-enabled'}}{{else}}{{_ 'keyboard-shortcuts-disabled'}}{{/if}}")
|
||||
i.fa(class="fa-solid fa-{{#if isKeyboardShortcuts}}check-square-o{{else}}ban{{/if}}")
|
||||
a.sidebar-xmark.js-close-sidebar ✕
|
||||
.sidebar-content.js-board-sidebar-content
|
||||
//a.hide-btn.js-hide-sidebar
|
||||
// i.fa.fa-navicon
|
||||
unless isDefaultView
|
||||
h2
|
||||
a.fa.fa-chevron-left.js-back-home
|
||||
= getViewTitle
|
||||
if isOpen
|
||||
+Template.dynamic(template=getViewTemplate)
|
||||
.sidebar-content.js-board-sidebar-content
|
||||
//a.hide-btn.js-hide-sidebar
|
||||
// i.fa.fa-navicon
|
||||
unless isDefaultView
|
||||
h2
|
||||
a.fa.fa-chevron-left.js-back-home
|
||||
= getViewTitle
|
||||
if isOpen
|
||||
+Template.dynamic(template=getViewTemplate)
|
||||
|
||||
template(name='homeSidebar')
|
||||
hr
|
||||
+membersWidget
|
||||
hr
|
||||
+labelsWidget
|
||||
hr
|
||||
ul#cards.label-text-hidden
|
||||
a.flex.js-toggle-minicard-label-text(title="{{_ 'hide-minicard-label-text'}}")
|
||||
span {{_ 'hide-minicard-label-text'}}
|
||||
b
|
||||
.materialCheckBox(class="{{#if hiddenMinicardLabelText}}is-checked{{/if}}")
|
||||
ul#cards.vertical-scrollbars-toggle
|
||||
a.flex.js-vertical-scrollbars-toggle(title="{{_ 'enable-vertical-scrollbars'}}")
|
||||
span {{_ 'enable-vertical-scrollbars'}}
|
||||
b
|
||||
.materialCheckBox(class="{{#if isVerticalScrollbars}}is-checked{{/if}}")
|
||||
hr
|
||||
unless currentUser.isNoComments
|
||||
h3
|
||||
h3.activity-title
|
||||
i.fa.fa-comments-o
|
||||
| {{_ 'activities'}}
|
||||
|
||||
.material-toggle-switch(title="{{_ 'show-activities'}}")
|
||||
if showActivities
|
||||
input.toggle-switch(type="checkbox" id="toggleShowActivitiesBoard" checked="checked")
|
||||
else
|
||||
input.toggle-switch(type="checkbox" id="toggleShowActivitiesBoard")
|
||||
label.toggle-label(for="toggleShowActivitiesBoard")
|
||||
+activities(mode="board")
|
||||
|
||||
template(name="membersWidget")
|
||||
@@ -40,11 +60,6 @@ template(name="membersWidget")
|
||||
a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}")
|
||||
i.board-header-btn-icon.fa.fa-cog
|
||||
| {{_ 'boardMenuPopup-title'}}
|
||||
.board-widget.board-widget-members
|
||||
.sidebar-shortcuts
|
||||
a.board-header-btn.js-shortcuts(title="{{_ 'keyboard-shortcuts' }}")
|
||||
i.fa.fa-keyboard-o
|
||||
span {{_ 'keyboard-shortcuts' }}
|
||||
hr
|
||||
h3
|
||||
i.fa.fa-users
|
||||
|
||||
@@ -105,6 +105,16 @@ BlazeComponent.extendComponent({
|
||||
else return `${TAPi18n.__('sidebar-open')}`;
|
||||
},
|
||||
|
||||
isKeyboardShortcuts() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
return user && user.isKeyboardShortcuts();
|
||||
},
|
||||
|
||||
isVerticalScrollbars() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
return user && user.isVerticalScrollbars();
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
@@ -126,6 +136,12 @@ BlazeComponent.extendComponent({
|
||||
'click .js-shortcuts'() {
|
||||
FlowRouter.go('shortcuts');
|
||||
},
|
||||
'click .js-keyboard-shortcuts-toggle'() {
|
||||
ReactiveCache.getCurrentUser().toggleKeyboardShortcuts();
|
||||
},
|
||||
'click .js-vertical-scrollbars-toggle'() {
|
||||
ReactiveCache.getCurrentUser().toggleVerticalScrollbars();
|
||||
},
|
||||
'click .js-close-sidebar'() {
|
||||
Sidebar.toggle()
|
||||
},
|
||||
@@ -136,7 +152,7 @@ BlazeComponent.extendComponent({
|
||||
|
||||
Blaze.registerHelper('Sidebar', () => Sidebar);
|
||||
|
||||
Template.homeSidebar.helpers({
|
||||
BlazeComponent.extendComponent({
|
||||
hiddenMinicardLabelText() {
|
||||
currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser) {
|
||||
@@ -147,7 +163,24 @@ Template.homeSidebar.helpers({
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
isVerticalScrollbars() {
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
return user && user.isVerticalScrollbars();
|
||||
},
|
||||
showActivities() {
|
||||
let ret = Utils.getCurrentBoard().showActivities ?? false;
|
||||
return ret;
|
||||
},
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click #toggleShowActivitiesBoard'() {
|
||||
Utils.getCurrentBoard().toggleShowActivities();
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
}).register('homeSidebar');
|
||||
|
||||
Template.boardInfoOnMyBoardsPopup.helpers({
|
||||
hideCardCounterList() {
|
||||
@@ -913,19 +946,11 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
allowsCreator() {
|
||||
return (
|
||||
this.currentBoard.allowsCreator === null ||
|
||||
this.currentBoard.allowsCreator === undefined ||
|
||||
this.currentBoard.allowsCreator
|
||||
);
|
||||
return this.currentBoard.allowsCreator ?? false;
|
||||
},
|
||||
|
||||
allowsCreatorOnMinicard() {
|
||||
return (
|
||||
this.currentBoard.allowsCreatorOnMinicard === null ||
|
||||
this.currentBoard.allowsCreatorOnMinicard === undefined ||
|
||||
this.currentBoard.allowsCreatorOnMinicard
|
||||
);
|
||||
return this.currentBoard.allowsCreatorOnMinicard ?? false;
|
||||
},
|
||||
|
||||
allowsMembers() {
|
||||
|
||||
@@ -24,9 +24,17 @@ template(name="swimlaneFixedHeader")
|
||||
| {{isTitleDefault title}}
|
||||
.swimlane-header-menu
|
||||
unless currentUser.isCommentOnly
|
||||
if currentUser.isBoardAdmin
|
||||
a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}")
|
||||
a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}")
|
||||
a.fa.fa-navicon.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}")
|
||||
//// TODO: Collapse Swimlane: make button working, etc.
|
||||
//unless collapsed
|
||||
// a.js-collapse-swimlane(title="{{_ 'collapse'}}")
|
||||
// i.fa.fa-arrow-down.swimlane-header-collapse-down
|
||||
// i.fa.fa-arrow-up.swimlane-header-collapse-up
|
||||
//if collapsed
|
||||
// a.js-collapse-swimlane(title="{{_ 'uncollapse'}}")
|
||||
// i.fa.fa-arrow-up.swimlane-header-collapse-up
|
||||
// i.fa.fa-arrow-down.swimlane-header-collapse-down
|
||||
unless isTouchScreen
|
||||
if isShowDesktopDragHandles
|
||||
a.swimlane-header-handle.handle.fa.fa-arrows.js-swimlane-header-handle
|
||||
|
||||
@@ -18,10 +18,25 @@ BlazeComponent.extendComponent({
|
||||
swimlane.rename(newTitle.trim());
|
||||
}
|
||||
},
|
||||
collapsed(check = undefined) {
|
||||
const swimlane = Template.currentData();
|
||||
const status = swimlane.isCollapsed();
|
||||
if (check === undefined) {
|
||||
// just check
|
||||
return status;
|
||||
} else {
|
||||
swimlane.collapse(!status);
|
||||
return !status;
|
||||
}
|
||||
},
|
||||
|
||||
events() {
|
||||
return [
|
||||
{
|
||||
'click .js-collapse-swimlane'(event) {
|
||||
event.preventDefault();
|
||||
this.collapsed(!this.collapsed());
|
||||
},
|
||||
'click .js-open-swimlane-menu': Popup.open('swimlaneAction'),
|
||||
'click .js-open-add-swimlane-menu': Popup.open('swimlaneAdd'),
|
||||
submit: this.editTitle,
|
||||
@@ -128,7 +143,7 @@ BlazeComponent.extendComponent({
|
||||
Swimlanes.insert({
|
||||
title,
|
||||
boardId: Session.get('currentBoard'),
|
||||
sort: sortValue.base,
|
||||
sort: sortValue.base || 0,
|
||||
type: swimlaneType,
|
||||
});
|
||||
|
||||
@@ -209,7 +224,7 @@ BlazeComponent.extendComponent({
|
||||
swimlaneHeightValue() {
|
||||
const swimlane = this.currentData();
|
||||
const board = swimlane.boardId;
|
||||
return Meteor.user().getSwimlaneHeight(board, swimlane._id);
|
||||
return ReactiveCache.getCurrentUser().getSwimlaneHeight(board, swimlane._id);
|
||||
},
|
||||
|
||||
events() {
|
||||
|
||||
@@ -1,43 +1,3 @@
|
||||
/*
|
||||
// Minimize swimlanes start https://www.w3schools.com/howto/howto_js_accordion.asp
|
||||
|
||||
.accordion
|
||||
cursor: pointer
|
||||
width: 30px
|
||||
height: 20px
|
||||
border: none
|
||||
outline: none
|
||||
font-size: 18px
|
||||
transition: 0.4s
|
||||
padding-top: 0px
|
||||
margin-top: 0px
|
||||
|
||||
.accordion:after
|
||||
// Unicode triagle right:
|
||||
content: '\25B6'
|
||||
color: #777
|
||||
font-weight: bold
|
||||
float: left
|
||||
|
||||
.active:after
|
||||
// Unicode triangle down:
|
||||
content: '\25BC'
|
||||
|
||||
.panel
|
||||
width: 100%
|
||||
max-height: 0
|
||||
overflow: hidden
|
||||
transition: max-height 0.2s ease-out
|
||||
margin: 0px
|
||||
padding: 0px
|
||||
|
||||
// Minimize swimlanes end https://www.w3schools.com/howto/howto_js_accordion.asp
|
||||
*/
|
||||
@media screen and (min-width: 801px) {
|
||||
.swimlane.ui-sortable {
|
||||
width: max-content;
|
||||
}
|
||||
}
|
||||
[class=swimlane] {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
@@ -46,7 +6,30 @@
|
||||
background: #dedede;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: 0;
|
||||
overflow: auto;
|
||||
max-height: 100%;
|
||||
}
|
||||
.swimlane-header-menu .swimlane-header-collapse-down {
|
||||
font-size: 50%;
|
||||
color: #a6a6a6;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 100px;
|
||||
}
|
||||
.swimlane-header-menu .swimlane-header-collapse-up {
|
||||
font-size: 50%;
|
||||
color: #a6a6a6;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
left: 100px;
|
||||
}
|
||||
.swimlane-header-menu .swimlane-header-uncollapse-up {
|
||||
font-size: 50%;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
.swimlane-header-menu .swimlane-header-uncollapse-down {
|
||||
font-size: 50%;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
.swimlane.placeholder {
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
@@ -170,7 +153,7 @@
|
||||
color: #4d4d4d !important;
|
||||
}
|
||||
.swimlane-silver {
|
||||
background: unset !important;
|
||||
background: #ccc !important;
|
||||
color: #4d4d4d !important;
|
||||
}
|
||||
.swimlane-peachpuff {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
template(name="swimlane")
|
||||
.swimlane
|
||||
.swimlane.nodragscroll
|
||||
+swimlaneHeader
|
||||
unless collapseSwimlane
|
||||
.swimlane.js-lists.js-swimlane(id="swimlane-{{_id}}"
|
||||
.swimlane.js-lists.js-swimlane.dragscroll(id="swimlane-{{_id}}"
|
||||
style="height:{{swimlaneHeight}};")
|
||||
if isMiniScreen
|
||||
if currentListIsInThisSwimlane _id
|
||||
@@ -24,7 +24,7 @@ template(name="swimlane")
|
||||
+cardDetails(currentCard)
|
||||
|
||||
template(name="listsGroup")
|
||||
.swimlane.list-group.js-lists
|
||||
.swimlane.list-group.js-lists.dragscroll
|
||||
if isMiniScreen
|
||||
if currentList
|
||||
+list(currentList)
|
||||
@@ -46,8 +46,8 @@ template(name="listsGroup")
|
||||
|
||||
template(name="addListForm")
|
||||
unless currentUser.isWorker
|
||||
.list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
|
||||
if currentUser.isBoardAdmin
|
||||
unless currentUser.isCommentOnly
|
||||
.list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
|
||||
.list-header-add
|
||||
+inlinedForm(autoclose=false)
|
||||
input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
|
||||
|
||||
@@ -225,7 +225,7 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
swimlaneHeight() {
|
||||
const user = Meteor.user();
|
||||
const user = ReactiveCache.getCurrentUser();
|
||||
const swimlane = Template.currentData();
|
||||
const height = user.getSwimlaneHeight(swimlane.boardId, swimlane._id);
|
||||
return height == -1 ? "auto" : (height + "px");
|
||||
@@ -288,7 +288,7 @@ BlazeComponent.extendComponent({
|
||||
|
||||
Template.swimlane.helpers({
|
||||
canSeeAddList() {
|
||||
return Meteor.user().isBoardAdmin();
|
||||
return ReactiveCache.getCurrentUser().isBoardAdmin();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
float: left;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
margin: .3vh;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
z-index: 1;
|
||||
|
||||
@@ -26,9 +26,9 @@ template(name="orgAvatar")
|
||||
template(name="boardOrgRow")
|
||||
tr
|
||||
if orgData.orgIsActive
|
||||
td <s>{{ orgData.orgDisplayName }}</s>
|
||||
else
|
||||
td {{ orgData.orgDisplayName }}
|
||||
else
|
||||
td <s>{{ orgData.orgDisplayName }}</s>
|
||||
td
|
||||
if currentUser.isBoardAdmin
|
||||
a.member.orgOrTeamMember.add-member.js-manage-board-removeOrg(title="{{_ 'remove-from-board'}}")
|
||||
@@ -39,9 +39,9 @@ template(name="boardOrgRow")
|
||||
template(name="boardTeamRow")
|
||||
tr
|
||||
if teamData.teamIsActive
|
||||
td <s>{{ teamData.teamDisplayName }}</s>
|
||||
else
|
||||
td {{ teamData.teamDisplayName }}
|
||||
else
|
||||
td <s>{{ teamData.teamDisplayName }}</s>
|
||||
td
|
||||
if currentUser.isBoardAdmin
|
||||
a.member.orgOrTeamMember.add-member.js-manage-board-removeTeam(title="{{_ 'remove-from-board'}}")
|
||||
|
||||
@@ -77,6 +77,10 @@ template(name="memberMenuPopup")
|
||||
a.js-change-language
|
||||
i.fa.fa-flag
|
||||
| {{_ 'changeLanguagePopup-title'}}
|
||||
//li
|
||||
// a.js-support
|
||||
// i.fa.fa-question-circle
|
||||
// | {{_ 'support'}}
|
||||
unless isSandstorm
|
||||
hr
|
||||
ul.pop-over-list
|
||||
@@ -139,6 +143,12 @@ template(name="editProfilePopup")
|
||||
div
|
||||
input#deleteButton.primary.wide(type="button" value="{{_ 'delete'}}")
|
||||
|
||||
|
||||
template(name="supportPopup")
|
||||
ul.pop-over-list
|
||||
li
|
||||
| Support popup text will be editable later.
|
||||
|
||||
template(name="changePasswordPopup")
|
||||
+atForm(state='changePwd')
|
||||
|
||||
@@ -153,12 +163,6 @@ template(name="changeLanguagePopup")
|
||||
|
||||
template(name="changeSettingsPopup")
|
||||
ul.pop-over-list
|
||||
//li
|
||||
// a.js-toggle-system-messages
|
||||
// i.fa.fa-comments-o
|
||||
// | {{_ 'hide-system-messages'}}
|
||||
// if hiddenSystemMessages
|
||||
// i.fa.fa-check
|
||||
//li
|
||||
// a.js-toggle-desktop-drag-handles
|
||||
// i.fa.fa-arrows
|
||||
|
||||
@@ -77,6 +77,7 @@ Template.memberMenuPopup.events({
|
||||
'click .js-change-avatar': Popup.open('changeAvatar'),
|
||||
'click .js-change-password': Popup.open('changePassword'),
|
||||
'click .js-change-language': Popup.open('changeLanguage'),
|
||||
'click .js-support': Popup.open('support'),
|
||||
'click .js-logout'(event) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -283,16 +284,6 @@ Template.changeLanguagePopup.events({
|
||||
});
|
||||
|
||||
Template.changeSettingsPopup.helpers({
|
||||
hiddenSystemMessages() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser) {
|
||||
return (currentUser.profile || {}).hasHiddenSystemMessages;
|
||||
} else if (window.localStorage.getItem('hasHiddenSystemMessages')) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
rescueCardDescription() {
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser) {
|
||||
@@ -325,7 +316,7 @@ Template.changeSettingsPopup.helpers({
|
||||
});
|
||||
},
|
||||
startDayOfWeek() {
|
||||
currentUser = Meteor.user();
|
||||
currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser) {
|
||||
return currentUser.getStartDayOfWeek();
|
||||
} else {
|
||||
@@ -343,7 +334,7 @@ Template.changeSettingsPopup.events({
|
||||
return ret;
|
||||
},
|
||||
'click .js-toggle-desktop-drag-handles'() {
|
||||
currentUser = Meteor.user();
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (currentUser) {
|
||||
Meteor.call('toggleDesktopDragHandles');
|
||||
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
|
||||
@@ -352,16 +343,6 @@ Template.changeSettingsPopup.events({
|
||||
window.localStorage.setItem('showDesktopDragHandles', 'true');
|
||||
}
|
||||
},
|
||||
'click .js-toggle-system-messages'() {
|
||||
currentUser = Meteor.user();
|
||||
if (currentUser) {
|
||||
Meteor.call('toggleSystemMessages');
|
||||
} else if (window.localStorage.getItem('hasHiddenSystemMessages')) {
|
||||
window.localStorage.removeItem('hasHiddenSystemMessages');
|
||||
} else {
|
||||
window.localStorage.setItem('hasHiddenSystemMessages', 'true');
|
||||
}
|
||||
},
|
||||
'click .js-rescue-card-description'() {
|
||||
Meteor.call('toggleRescueCardDescription')
|
||||
},
|
||||
@@ -375,7 +356,7 @@ Template.changeSettingsPopup.events({
|
||||
templateInstance.$('#start-day-of-week').val(),
|
||||
10,
|
||||
);
|
||||
const currentUser = Meteor.user();
|
||||
const currentUser = ReactiveCache.getCurrentUser();
|
||||
if (isNaN(minLimit) || minLimit < -1) {
|
||||
minLimit = -1;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ Blaze.registerHelper('isTouchScreenOrShowDesktopDragHandles', () =>
|
||||
Blaze.registerHelper('moment', (...args) => {
|
||||
args.pop(); // hash
|
||||
const [date, format] = args;
|
||||
return moment(date).format(format);
|
||||
return moment(date).format(format ?? 'LLLL');
|
||||
});
|
||||
|
||||
Blaze.registerHelper('canModifyCard', () =>
|
||||
|
||||
@@ -6,10 +6,11 @@ import moment from 'moment/min/moment-with-locales';
|
||||
function adjustedTimeFormat() {
|
||||
return moment
|
||||
.localeData()
|
||||
.longDateFormat('LT')
|
||||
.replace(/HH/i, 'H');
|
||||
.longDateFormat('LT');
|
||||
}
|
||||
|
||||
// .replace(/HH/i, 'H');
|
||||
|
||||
export class DatePicker extends BlazeComponent {
|
||||
template() {
|
||||
return 'datepicker';
|
||||
|
||||
@@ -123,6 +123,7 @@ EscapeActions = {
|
||||
// the shortcut sould work on textarea and inputs as well.
|
||||
Mousetrap.bindGlobal('esc', () => {
|
||||
EscapeActions.executeLowest();
|
||||
Sidebar.hide();
|
||||
});
|
||||
|
||||
// On a left click on the document, we try to exectute one escape action (eg,
|
||||
|
||||
@@ -237,7 +237,6 @@ class SetFilter {
|
||||
if (this._indexOfVal(val) === -1) {
|
||||
this._selectedElements.push(val);
|
||||
this._dep.changed();
|
||||
showFilterSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+114
-2
@@ -3,6 +3,46 @@ import { ReactiveCache } from '/imports/reactiveCache';
|
||||
// XXX There is no reason to define these shortcuts globally, they should be
|
||||
// attached to a template (most of them will go in the `board` template).
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
// Only handle event if coming from body
|
||||
if (e.target !== document.body) return;
|
||||
|
||||
// Only handle event if it's in another language
|
||||
if (String.fromCharCode(e.which).toLowerCase() === e.key) return;
|
||||
|
||||
// Trigger the corresponding action
|
||||
Mousetrap.handleKey(String.fromCharCode(e.which).toLowerCase(), [], {type: "keypress"});
|
||||
});
|
||||
|
||||
// Overwrite the stopCallback to allow for more keyboard shortcut customizations
|
||||
Mousetrap.stopCallback = (event, element) => {
|
||||
// Are shortcuts enabled for the user?
|
||||
if (ReactiveCache.getCurrentUser() && !ReactiveCache.getCurrentUser().isKeyboardShortcuts())
|
||||
return true;
|
||||
|
||||
// Always handle escape
|
||||
if (event.keyCode === 27)
|
||||
return false;
|
||||
|
||||
// Make sure there are no selected characters
|
||||
if (window.getSelection().type === "Range")
|
||||
return true;
|
||||
|
||||
// Decide what the current element is
|
||||
const currentElement = event.target || document.activeElement;
|
||||
|
||||
// If the current element is editable, we don't want to trigger an event
|
||||
if (currentElement.isContentEditable)
|
||||
return true;
|
||||
|
||||
// Make sure we are not in an input element
|
||||
if (currentElement instanceof HTMLInputElement || currentElement instanceof HTMLSelectElement || currentElement instanceof HTMLTextAreaElement)
|
||||
return true;
|
||||
|
||||
// We can trigger events!
|
||||
return false;
|
||||
}
|
||||
|
||||
function getHoveredCardId() {
|
||||
const card = $('.js-minicard:hover').get(0);
|
||||
if (!card) return null;
|
||||
@@ -33,6 +73,14 @@ Mousetrap.bind('q', () => {
|
||||
}
|
||||
});
|
||||
|
||||
Mousetrap.bind('a', () => {
|
||||
const currentBoardId = Session.get('currentBoard');
|
||||
const currentUserId = Meteor.userId();
|
||||
if (currentBoardId && currentUserId) {
|
||||
Filter.assignees.toggle(currentUserId);
|
||||
}
|
||||
});
|
||||
|
||||
Mousetrap.bind('x', () => {
|
||||
if (Filter.isActive()) {
|
||||
Filter.reset();
|
||||
@@ -131,6 +179,26 @@ Mousetrap.bind(numArray, (evt, key) => {
|
||||
}
|
||||
});
|
||||
|
||||
Mousetrap.bind('m', evt => {
|
||||
const cardId = getSelectedCardId();
|
||||
if (!cardId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentUserId = Meteor.userId();
|
||||
if (currentUserId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ReactiveCache.getCurrentUser().isBoardMember()) {
|
||||
const card = ReactiveCache.getCard(cardId);
|
||||
card.toggleAssignee(currentUserId);
|
||||
// We should prevent scrolling in card when spacebar is clicked
|
||||
// This should do it according to Mousetrap docs, but it doesn't
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
Mousetrap.bind('space', evt => {
|
||||
const cardId = getSelectedCardId();
|
||||
if (!cardId) {
|
||||
@@ -151,7 +219,7 @@ Mousetrap.bind('space', evt => {
|
||||
}
|
||||
});
|
||||
|
||||
Mousetrap.bind('c', evt => {
|
||||
const archiveCard = evt => {
|
||||
const cardId = getSelectedCardId();
|
||||
if (!cardId) {
|
||||
return;
|
||||
@@ -165,6 +233,38 @@ Mousetrap.bind('c', evt => {
|
||||
if (Utils.canModifyBoard()) {
|
||||
const card = ReactiveCache.getCard(cardId);
|
||||
card.archive();
|
||||
// We should prevent scrolling in card when spacebar is clicked
|
||||
// This should do it according to Mousetrap docs, but it doesn't
|
||||
evt.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
// Archive card has multiple shortcuts
|
||||
Mousetrap.bind('c', archiveCard);
|
||||
Mousetrap.bind('-', archiveCard);
|
||||
|
||||
// Same as above, this time for Persian keyboard.
|
||||
// https://github.com/wekan/wekan/pull/5589#issuecomment-2516776519
|
||||
Mousetrap.bind('÷', archiveCard);
|
||||
|
||||
Mousetrap.bind('n', evt => {
|
||||
const cardId = getSelectedCardId();
|
||||
if (!cardId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentUserId = Meteor.userId();
|
||||
if (currentUserId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Utils.canModifyBoard()) {
|
||||
// Find the current hovered card
|
||||
const card = ReactiveCache.getCard(cardId);
|
||||
|
||||
// Find the button and click it
|
||||
$(`#js-list-${card.listId} .list-body .minicards .open-minicard-composer`).click();
|
||||
|
||||
// We should prevent scrolling in card when spacebar is clicked
|
||||
// This should do it according to Mousetrap docs, but it doesn't
|
||||
evt.preventDefault();
|
||||
@@ -181,6 +281,14 @@ Template.keyboardShortcuts.helpers({
|
||||
keys: ['q'],
|
||||
action: 'shortcut-filter-my-cards',
|
||||
},
|
||||
{
|
||||
keys: ['a'],
|
||||
action: 'shortcut-filter-my-assigned-cards',
|
||||
},
|
||||
{
|
||||
keys: ['n'],
|
||||
action: 'add-card-to-bottom-of-list',
|
||||
},
|
||||
{
|
||||
keys: ['f'],
|
||||
action: 'shortcut-toggle-filterbar',
|
||||
@@ -207,10 +315,14 @@ Template.keyboardShortcuts.helpers({
|
||||
},
|
||||
{
|
||||
keys: ['SPACE'],
|
||||
action: 'shortcut-add-self',
|
||||
},
|
||||
{
|
||||
keys: ['m'],
|
||||
action: 'shortcut-assign-self',
|
||||
},
|
||||
{
|
||||
keys: ['c'],
|
||||
keys: ['c', '÷', '-'],
|
||||
action: 'archive-card',
|
||||
},
|
||||
{
|
||||
|
||||
+4
-3
@@ -4,10 +4,11 @@ Utils = {
|
||||
setBackgroundImage(url) {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
if (currentBoard.backgroundImageURL !== undefined) {
|
||||
$(".board-wrapper,.board-wrapper .board-canvas").css({"background":"url(" + currentBoard.backgroundImageURL + ")","background-size":"cover"});
|
||||
$(".board-wrapper").css({"background":"url(" + currentBoard.backgroundImageURL + ")","background-size":"cover"});
|
||||
$(".swimlane,.swimlane .list,.swimlane .list .list-body,.swimlane .list:first-child .list-body").css({"background-color":"transparent"});
|
||||
} else if (currentBoard.color !== undefined) {
|
||||
currentBoard.setColor(currentBoard.color);
|
||||
$(".minicard").css({"opacity": "0.9"});
|
||||
} else if (currentBoard["background-color"]) {
|
||||
currentBoard.setColor(currentBoard["background-color"]);
|
||||
}
|
||||
},
|
||||
/** returns the current board id
|
||||
|
||||
@@ -16,6 +16,8 @@ export const ALLOWED_BOARD_COLORS = [
|
||||
'modern',
|
||||
'moderndark',
|
||||
'exodark',
|
||||
'cleandark',
|
||||
'cleanlight',
|
||||
];
|
||||
export const ALLOWED_COLORS = [
|
||||
'white',
|
||||
|
||||
@@ -55,6 +55,30 @@ FlowRouter.route('/public', {
|
||||
},
|
||||
});
|
||||
|
||||
FlowRouter.route('/accessibility', {
|
||||
name: 'accessibility',
|
||||
triggersEnter: [AccountsTemplates.ensureSignedIn],
|
||||
action() {
|
||||
Session.set('currentBoard', null);
|
||||
Session.set('currentList', null);
|
||||
Session.set('currentCard', null);
|
||||
Session.set('popupCardId', null);
|
||||
Session.set('popupCardBoardId', null);
|
||||
|
||||
Filter.reset();
|
||||
Session.set('sortBy', '');
|
||||
EscapeActions.executeAll();
|
||||
|
||||
Utils.manageCustomUI();
|
||||
Utils.manageMatomo();
|
||||
|
||||
BlazeLayout.render('defaultLayout', {
|
||||
headerBar: 'accessibilityHeaderBar',
|
||||
content: 'accessibility',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
FlowRouter.route('/b/:id/:slug', {
|
||||
name: 'board',
|
||||
action(params) {
|
||||
|
||||
+13
-11
@@ -1,5 +1,3 @@
|
||||
version: '2'
|
||||
|
||||
# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required.
|
||||
#---------------------------------------------------------------------------------------------------------
|
||||
# ==== CREATING USERS AND LOGGING IN TO WEKAN ====
|
||||
@@ -12,13 +10,13 @@ version: '2'
|
||||
# NOTE: MongoDB has changed from 3.x to 4.x, in that case you need backup/restore with --noIndexRestore
|
||||
# see https://github.com/wekan/wekan/wiki/Backup
|
||||
# 1) Stop Wekan:
|
||||
# docker-compose stop
|
||||
# docker compose stop
|
||||
# 2) Remove old Wekan app (wekan-app only, not that wekan-db container that has all your data)
|
||||
# docker rm wekan-app
|
||||
# 3) Get newest docker-compose.yml from https://github.com/wekan/wekan to have correct image,
|
||||
# for example: "image: quay.io/wekan/wekan" or version tag "image: quay.io/wekan/wekan:v4.52"
|
||||
# 4) Start Wekan:
|
||||
# docker-compose up -d
|
||||
# docker compose up -d
|
||||
#----------------------------------------------------------------------------------
|
||||
# ==== OPTIONAL: DEDICATED DOCKER USER ====
|
||||
# 1) Optionally create a dedicated user for Wekan, for example:
|
||||
@@ -38,14 +36,14 @@ version: '2'
|
||||
# ----------------------------------------------------------------------------------
|
||||
# ==== USAGE OF THIS docker-compose.yml ====
|
||||
# 1) For seeing does Wekan work, try this and check with your web browser:
|
||||
# docker-compose up
|
||||
# docker compose up
|
||||
# 2) Stop Wekan and start Wekan in background:
|
||||
# docker-compose stop
|
||||
# docker-compose up -d
|
||||
# docker compose stop
|
||||
# docker compose up -d
|
||||
# 3) See running Docker containers:
|
||||
# docker ps
|
||||
# 4) Stop Docker containers:
|
||||
# docker-compose stop
|
||||
# docker compose stop
|
||||
# ----------------------------------------------------------------------------------
|
||||
# ===== INSIDE DOCKER CONTAINERS, AND BACKUP/RESTORE ====
|
||||
# https://github.com/wekan/wekan/wiki/Backup
|
||||
@@ -142,7 +140,7 @@ services:
|
||||
- wekan-tier
|
||||
#-------------------------------------------------------------------------------------
|
||||
# ==== BUILD wekan-app DOCKER CONTAINER FROM SOURCE, if you uncomment these ====
|
||||
# ==== and use commands: docker-compose up -d --build
|
||||
# ==== and use commands: docker compose up -d --build
|
||||
#build:
|
||||
# context: .
|
||||
# dockerfile: Dockerfile
|
||||
@@ -444,11 +442,15 @@ services:
|
||||
# OAuth2 login style: popup or redirect.
|
||||
#- OAUTH2_LOGIN_STYLE=redirect
|
||||
#- OAUTH2_CLIENT_ID=<Keycloak create Client ID>
|
||||
#- OAUTH2_SERVER_URL=<Keycloak server name>/auth
|
||||
#- OAUTH2_SERVER_URL=<Keycloak server url - https://keycloak.example.com>
|
||||
#- OAUTH2_AUTH_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/auth
|
||||
#- OAUTH2_USERINFO_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/userinfo
|
||||
#- OAUTH2_TOKEN_ENDPOINT=/realms/<keycloak realm>/protocol/openid-connect/token
|
||||
#- OAUTH2_SECRET=<keycloak client secret>
|
||||
#- OAUTH2_ID_MAP=sub
|
||||
#- OAUTH2_USERNAME_MAP=preferred_username
|
||||
#- OAUTH2_EMAIL_MAP=email
|
||||
#- OAUTH2_FULLNAME_MAP=name
|
||||
#-----------------------------------------------------------------
|
||||
# ==== OAUTH2 DOORKEEPER ====
|
||||
# https://github.com/wekan/wekan/issues/1874
|
||||
@@ -587,7 +589,7 @@ services:
|
||||
# If using LDAPS: LDAP_ENCRYPTION=ssl
|
||||
#- LDAP_ENCRYPTION=false
|
||||
#
|
||||
# The certification for the LDAPS server. Certificate needs to be included in this docker-compose.yml file.
|
||||
# The certification for the LDAPS server. Certificate needs to be included in this docker compose.yml file.
|
||||
#- LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+G2FIdAgIC...-----END CERTIFICATE-----
|
||||
#
|
||||
# Reject Unauthorized Certificate
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
Wekan provides a python script to ease the call of the REST API from command line interface.
|
||||
|
||||
# Context
|
||||
|
||||
- [API Login to get Bearer token](REST-API#example-call---as-form-data)
|
||||
- [API docs and examples for various programming languages](https://wekan.github.io/api/), there is Boards / Export for exporting board with API
|
||||
- In the right menu, scroll down REST API Docs etc links =====>
|
||||
- Wekan-Gogs integration with Node.js https://github.com/wekan/wekan-gogs
|
||||
|
||||
# Install
|
||||
|
||||
You need python3.
|
||||
|
||||
Windows
|
||||
```
|
||||
choco install python3
|
||||
# REBOOT
|
||||
pip3 install pip --upgrade
|
||||
pip3 install json
|
||||
python3 wekan.py
|
||||
```
|
||||
Debian/Ubuntu
|
||||
```
|
||||
sudo apt-get -y install python3 python3-pip python3-simplejson
|
||||
sudo pip3 install pip --upgrade
|
||||
chmod +x wekan.py
|
||||
./wekan.py
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
Copy the api.py script to you machine. [Newest Wekan Python CLI api.py here](https://raw.githubusercontent.com/wekan/wekan/master/api.py).
|
||||
|
||||
Then, in this script, look for and change:
|
||||
- wekanurl: https://boards.example.com => Your Wekan URL
|
||||
- username (could be username or username@example.com)
|
||||
- Only works with password login admin user. Does not work with LDAP, OAuth2 etc.
|
||||
|
||||
Keep in mind your Wekan credentials are potentially accessible in this file.
|
||||
|
||||
Then call it without any argument to see if everything is all right. You should just get usage examples.
|
||||
@@ -0,0 +1,56 @@
|
||||
# Disclaimer
|
||||
|
||||
This page tries to be as up to date as possible. If you see something wrong here, feel free to update the page and help other people like you, that greatly depends on our APIs. If you don't feel comfortable doing this kind of changes, please contact us by creating an [issue](https://github.com/wekan/wekan/issues/new).
|
||||
|
||||
## Information about boards of user
|
||||
```
|
||||
curl -H "Authorization: Bearer a6DM_gOPRwBdynfXaGBaiiEwTiAuigR_Fj_81QmNpnf" \
|
||||
http://localhost:3000/api/users/XQMZgynx9M79qTtQc/boards
|
||||
```
|
||||
|
||||
## Add/Remove Board Member and Change Role
|
||||
|
||||
[Add/Remove Board Member and Change Role admin/normal/nocomments/commentonly](REST-API-Role).
|
||||
|
||||
## The admin takes the ownership of ALL boards of the user (archived and not archived) where the user is admin on.
|
||||
|
||||
| URL | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| `/api/users/:id` | `yes` | `PUT` |
|
||||
|
||||
```shell
|
||||
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
|
||||
-H "Content-type:application/json" \
|
||||
-X PUT \
|
||||
http://localhost:3000/api/users/ztKvBTzCqmyJ77on8 \
|
||||
-d '{ "action": "takeOwnership" }'
|
||||
```
|
||||
|
||||
## Create board
|
||||
|
||||
Required:
|
||||
- "title":"Board title here"
|
||||
- "owner":"ABCDE12345" <= User ID in Wekan. Not username or email.
|
||||
|
||||
Optional, and defaults:
|
||||
- "isAdmin":"true"
|
||||
- "isActive":"true"
|
||||
- "isNoComments":"false"
|
||||
- "isCommentOnly":"false"
|
||||
- "permission":"private" <== Set to "public" if you want public Wekan board
|
||||
- "color":"belize" <== Board color: belize, nephritis, pomegranate, pumpkin, wisteria, midnight.
|
||||
|
||||
<img src="https://wekan.github.io/board-colors.png" width="40%" alt="Wekan logo" />
|
||||
|
||||
Example:
|
||||
```
|
||||
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
|
||||
-H "Content-type:application/json" \
|
||||
-X POST \
|
||||
http://localhost:3000/api/boards \
|
||||
-d '{"title":"Board title here","owner":"ABCDE12345","permission":"private","color":"nephritis"}'
|
||||
```
|
||||
|
||||
## In Wekan code
|
||||
|
||||
If you believe that code is the best documentation, be our guest: [models/cards.js](https://github.com/wekan/wekan/blob/main/models/boards.js "Board API code")
|
||||
@@ -0,0 +1,94 @@
|
||||
# Disclaimer
|
||||
|
||||
This page tries to be as up to date as possible. If you see something wrong here, feel free to update the page and help other people like you, that greatly depends on our APIs. If you don't feel comfortable doing this kind of changes, please contact us by creating an [issue](https://github.com/wekan/wekan/issues/new).
|
||||
|
||||
# Retrieve cards by swimlane id
|
||||
|
||||
| API URL / Code Link | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| [/api/boards/:boardId/swimlanes/:swimlaneId/cards](https://github.com/wekan/wekan/blob/c115046a7c86b30ab5deb8762d3ef7a5ea3f4f90/models/cards.js#L487) | `yes` | `GET` |
|
||||
|
||||
```shell
|
||||
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
|
||||
-X GET \
|
||||
http://localhost:3000/api/boards/YRgy7Ku6uLFv2pYwZ/swimlanes/PgTuf6sFJsaxto5dC/cards
|
||||
```
|
||||
## Result example
|
||||
|
||||
```shell
|
||||
{
|
||||
"_id": "AzEeHS7KAGeYZCcak",
|
||||
"title": "Create Auth Code",
|
||||
"description": "Create Auth Code for application.",
|
||||
"listId": "RPRtDTQMKpShpgqoj"
|
||||
},
|
||||
{
|
||||
...
|
||||
```
|
||||
|
||||
# Add Card to List-Board-Swimlane
|
||||
|
||||
| API URL / Code Link | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| [/api/boards/:boardId/lists/:listId/cards](https://github.com/wekan/wekan/blob/c115046a7c86b30ab5deb8762d3ef7a5ea3f4f90/models/cards.js#L487) | `yes` | `POST` |
|
||||
|
||||
```shell
|
||||
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
|
||||
-H "Content-type:application/json" \
|
||||
-X POST \
|
||||
http://localhost:3000/api/boards/YRgy7Ku6uLFv2pYwZ/lists/PgTuf6sFJsaxto5dC/cards \
|
||||
-d '{ "title": "Card title text", "description": "Card description text", "authorId": "The appropriate existing userId", "swimlaneId": "The destination swimlaneId" }'
|
||||
```
|
||||
## Result example
|
||||
The new card's ID is returned in the format:
|
||||
```json
|
||||
{
|
||||
"_id": "W9m9YxQKT6zZrKzRW"
|
||||
}
|
||||
```
|
||||
|
||||
# Update a card
|
||||
You can change (any of) the card's title, list, and description.
|
||||
|
||||
| API URL / Code Link | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| [/api/boards/:boardId/lists/:fromListId/cards/:cardId](https://github.com/wekan/wekan/blob/c115046a7c86b30ab5deb8762d3ef7a5ea3f4f90/models/cards.js#L520) | `yes` | `PUT` |
|
||||
|
||||
```shell
|
||||
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
|
||||
-H "Content-type:application/json" \
|
||||
-X PUT \
|
||||
http://localhost:3000/api/boards/YRgy7Ku6uLFv2pYwZ/lists/PgTuf6sFJsaxto5dC/cards/ssrNX9CvXvPxuC5DE \
|
||||
-d '{ "title": "New title text", "listId": "New destination listId", "description": "New description text" }'
|
||||
```
|
||||
## Result example
|
||||
The card's ID is returned in the format:
|
||||
```json
|
||||
{
|
||||
"_id": "W9m9YxQKT6zZrKzRW"
|
||||
}
|
||||
```
|
||||
# Delete a card
|
||||
|
||||
| API URL / Code Link | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| [/api/boards/:boardId/lists/:listId/cards/:cardId](https://github.com/wekan/wekan/blob/c115046a7c86b30ab5deb8762d3ef7a5ea3f4f90/models/cards.js#L554) | `yes` | `DELETE` |
|
||||
|
||||
```shell
|
||||
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
|
||||
-H "Content-type:application/json" \
|
||||
-X DELETE \
|
||||
http://localhost:3000/api/boards/YRgy7Ku6uLFv2pYwZ/lists/PgTuf6sFJsaxto5dC/cards/ssrNX9CvXvPxuC5DE \
|
||||
-d '{ "authorId": "the appropriate existing userId"}'
|
||||
```
|
||||
## Result example
|
||||
The card's ID is returned in the format:
|
||||
```json
|
||||
{
|
||||
"_id": "W9m9YxQKT6zZrKzRW"
|
||||
}
|
||||
```
|
||||
|
||||
# In Wekan code
|
||||
|
||||
If you believe that code is the best documentation, be our guest: [models/cards.js](https://github.com/wekan/wekan/blob/main/models/cards.js "Card API code")
|
||||
@@ -0,0 +1,148 @@
|
||||
## In Wekan code
|
||||
|
||||
## 1) Checklists
|
||||
|
||||
wekan/models/checklists.js at bottom:
|
||||
|
||||
```
|
||||
JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists', function (req, res) {
|
||||
Authentication.checkUserId( req.userId);
|
||||
const paramCardId = req.params.cardId;
|
||||
const checklists = Checklists.find({ cardId: paramCardId }).map(function (doc) {
|
||||
return {
|
||||
_id: doc._id,
|
||||
title: doc.title,
|
||||
};
|
||||
});
|
||||
if (checklists) {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: checklists,
|
||||
});
|
||||
} else {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 500,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', function (req, res) {
|
||||
Authentication.checkUserId( req.userId);
|
||||
const paramChecklistId = req.params.checklistId;
|
||||
const paramCardId = req.params.cardId;
|
||||
const checklist = Checklists.findOne({ _id: paramChecklistId, cardId: paramCardId });
|
||||
if (checklist) {
|
||||
checklist.items = ChecklistItems.find({checklistId: checklist._id}).map(function (doc) {
|
||||
return {
|
||||
_id: doc._id,
|
||||
title: doc.title,
|
||||
isFinished: doc.isFinished,
|
||||
};
|
||||
});
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: checklist,
|
||||
});
|
||||
} else {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 500,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
JsonRoutes.add('POST', '/api/boards/:boardId/cards/:cardId/checklists', function (req, res) {
|
||||
Authentication.checkUserId( req.userId);
|
||||
|
||||
const paramCardId = req.params.cardId;
|
||||
const id = Checklists.insert({
|
||||
title: req.body.title,
|
||||
cardId: paramCardId,
|
||||
sort: 0,
|
||||
});
|
||||
if (id) {
|
||||
req.body.items.forEach(function (item, idx) {
|
||||
ChecklistItems.insert({
|
||||
cardId: paramCardId,
|
||||
checklistId: id,
|
||||
title: item.title,
|
||||
sort: idx,
|
||||
});
|
||||
});
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: {
|
||||
_id: id,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 400,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
JsonRoutes.add('DELETE', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', function (req, res) {
|
||||
Authentication.checkUserId( req.userId);
|
||||
const paramChecklistId = req.params.checklistId;
|
||||
Checklists.remove({ _id: paramChecklistId });
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: {
|
||||
_id: paramChecklistId,
|
||||
},
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 2) Checklist Items
|
||||
|
||||
wekan/models/checklistItems.js at bottom:
|
||||
|
||||
```
|
||||
JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function (req, res) {
|
||||
Authentication.checkUserId( req.userId);
|
||||
const paramItemId = req.params.itemId;
|
||||
const checklistItem = ChecklistItems.findOne({ _id: paramItemId });
|
||||
if (checklistItem) {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: checklistItem,
|
||||
});
|
||||
} else {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 500,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
JsonRoutes.add('PUT', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function (req, res) {
|
||||
Authentication.checkUserId( req.userId);
|
||||
|
||||
const paramItemId = req.params.itemId;
|
||||
|
||||
if (req.body.hasOwnProperty('isFinished')) {
|
||||
ChecklistItems.direct.update({_id: paramItemId}, {$set: {isFinished: req.body.isFinished}});
|
||||
}
|
||||
if (req.body.hasOwnProperty('title')) {
|
||||
ChecklistItems.direct.update({_id: paramItemId}, {$set: {title: req.body.title}});
|
||||
}
|
||||
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: {
|
||||
_id: paramItemId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
JsonRoutes.add('DELETE', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function (req, res) {
|
||||
Authentication.checkUserId( req.userId);
|
||||
const paramItemId = req.params.itemId;
|
||||
ChecklistItems.direct.remove({ _id: paramItemId });
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: {
|
||||
_id: paramItemId,
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,33 @@
|
||||
## Using Meteor with REST API
|
||||
|
||||
### 1) Adding API
|
||||
|
||||
```
|
||||
meteor add simple:json-routes
|
||||
```
|
||||
|
||||
https://atmospherejs.com/simple/json-routes
|
||||
|
||||
https://github.com/wekan/wekan/blob/main/.meteor/packages#L139
|
||||
|
||||
### 2) API code
|
||||
|
||||
At models directory:
|
||||
|
||||
https://github.com/wekan/wekan/blob/main/models/users.js#L2018
|
||||
|
||||
### 3) Login to API as JSON
|
||||
|
||||
https://github.com/wekan/wekan/wiki/REST-API#example-call---as-json
|
||||
|
||||
Also see at wiki right menu about REST API.
|
||||
|
||||
### 4) Use Python3 CLI to login to API and do REST API action
|
||||
|
||||
https://github.com/wekan/wekan/blob/main/api.py
|
||||
|
||||
### 4) API Docs
|
||||
|
||||
https://wekan.github.io/api/
|
||||
|
||||
Building API Docs: https://github.com/wekan/wekan/blob/main/releases/rebuild-docs.sh
|
||||
@@ -0,0 +1,78 @@
|
||||
1) Login as Admin user as Form Data to get Bearer token
|
||||
https://github.com/wekan/wekan/wiki/REST-API#example-call---as-form-data
|
||||
|
||||
2) There needs to be Custom Field added to board
|
||||
https://wekan.github.io/api/v4.42/#wekan-rest-api-customfields
|
||||
|
||||
3) Custom Field at board and card will have same `_id`
|
||||
https://wekan.github.io/api/v4.42/#put_board_list_card
|
||||
|
||||
4) When writing Custom Field value to card, like text field, content type needs to be "application/json" and the string needs to be an array:
|
||||
```
|
||||
-d '{ "customFields" : [ { "_id" : "oZHkpcaxDHnbkbqGo", "value" : "foobar" } ] }'
|
||||
```
|
||||
5) For other types of Custom Fields, you can look at Custom Field structure from MongoDB database with [nosqlbooster](https://nosqlbooster.com/downloads) that can also login through ssh to server Wekan snap MongoDB port 27019
|
||||
|
||||
|
||||
## In Wekan code (old)
|
||||
|
||||
wekan/models/customFields.js , at bottom
|
||||
|
||||
```
|
||||
//CUSTOM FIELD REST API
|
||||
if (Meteor.isServer) {
|
||||
JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields', function (req, res) {
|
||||
Authentication.checkUserId( req.userId);
|
||||
const paramBoardId = req.params.boardId;
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: CustomFields.find({ boardId: paramBoardId }),
|
||||
});
|
||||
});
|
||||
|
||||
JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields/:customFieldId', function (req, res) {
|
||||
Authentication.checkUserId( req.userId);
|
||||
const paramBoardId = req.params.boardId;
|
||||
const paramCustomFieldId = req.params.customFieldId;
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: CustomFields.findOne({ _id: paramCustomFieldId, boardId: paramBoardId }),
|
||||
});
|
||||
});
|
||||
|
||||
JsonRoutes.add('POST', '/api/boards/:boardId/custom-fields', function (req, res) {
|
||||
Authentication.checkUserId( req.userId);
|
||||
const paramBoardId = req.params.boardId;
|
||||
const id = CustomFields.direct.insert({
|
||||
name: req.body.name,
|
||||
type: req.body.type,
|
||||
settings: req.body.settings,
|
||||
showOnCard: req.body.showOnCard,
|
||||
boardId: paramBoardId,
|
||||
});
|
||||
|
||||
const customField = CustomFields.findOne({_id: id, boardId: paramBoardId });
|
||||
customFieldCreation(req.body.authorId, customField);
|
||||
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: {
|
||||
_id: id,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
JsonRoutes.add('DELETE', '/api/boards/:boardId/custom-fields/:customFieldId', function (req, res) {
|
||||
Authentication.checkUserId( req.userId);
|
||||
const paramBoardId = req.params.boardId;
|
||||
const id = req.params.customFieldId;
|
||||
CustomFields.remove({ _id: id, boardId: paramBoardId });
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: {
|
||||
_id: id,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,191 @@
|
||||
## In Wekan code
|
||||
|
||||
wekan/models/integrations.js at bottom
|
||||
|
||||
```
|
||||
// Get all integrations in board
|
||||
JsonRoutes.add('GET', '/api/boards/:boardId/integrations', function(req, res) {
|
||||
try {
|
||||
const paramBoardId = req.params.boardId;
|
||||
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||
|
||||
const data = Integrations.find({ boardId: paramBoardId }, { fields: { token: 0 } }).map(function(doc) {
|
||||
return doc;
|
||||
});
|
||||
|
||||
JsonRoutes.sendResult(res, {code: 200, data});
|
||||
}
|
||||
catch (error) {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get a single integration in board
|
||||
JsonRoutes.add('GET', '/api/boards/:boardId/integrations/:intId', function(req, res) {
|
||||
try {
|
||||
const paramBoardId = req.params.boardId;
|
||||
const paramIntId = req.params.intId;
|
||||
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: Integrations.findOne({ _id: paramIntId, boardId: paramBoardId }, { fields: { token: 0 } }),
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Create a new integration
|
||||
JsonRoutes.add('POST', '/api/boards/:boardId/integrations', function(req, res) {
|
||||
try {
|
||||
const paramBoardId = req.params.boardId;
|
||||
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||
|
||||
const id = Integrations.insert({
|
||||
userId: req.userId,
|
||||
boardId: paramBoardId,
|
||||
url: req.body.url,
|
||||
});
|
||||
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: {
|
||||
_id: id,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Edit integration data
|
||||
JsonRoutes.add('PUT', '/api/boards/:boardId/integrations/:intId', function (req, res) {
|
||||
try {
|
||||
const paramBoardId = req.params.boardId;
|
||||
const paramIntId = req.params.intId;
|
||||
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||
|
||||
if (req.body.hasOwnProperty('enabled')) {
|
||||
const newEnabled = req.body.enabled;
|
||||
Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
|
||||
{$set: {enabled: newEnabled}});
|
||||
}
|
||||
if (req.body.hasOwnProperty('title')) {
|
||||
const newTitle = req.body.title;
|
||||
Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
|
||||
{$set: {title: newTitle}});
|
||||
}
|
||||
if (req.body.hasOwnProperty('url')) {
|
||||
const newUrl = req.body.url;
|
||||
Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
|
||||
{$set: {url: newUrl}});
|
||||
}
|
||||
if (req.body.hasOwnProperty('token')) {
|
||||
const newToken = req.body.token;
|
||||
Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
|
||||
{$set: {token: newToken}});
|
||||
}
|
||||
if (req.body.hasOwnProperty('activities')) {
|
||||
const newActivities = req.body.activities;
|
||||
Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
|
||||
{$set: {activities: newActivities}});
|
||||
}
|
||||
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: {
|
||||
_id: paramIntId,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Delete subscribed activities
|
||||
JsonRoutes.add('DELETE', '/api/boards/:boardId/integrations/:intId/activities', function (req, res) {
|
||||
try {
|
||||
const paramBoardId = req.params.boardId;
|
||||
const paramIntId = req.params.intId;
|
||||
const newActivities = req.body.activities;
|
||||
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||
|
||||
Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
|
||||
{$pullAll: {activities: newActivities}});
|
||||
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: Integrations.findOne({_id: paramIntId, boardId: paramBoardId}, { fields: {_id: 1, activities: 1}}),
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add subscribed activities
|
||||
JsonRoutes.add('POST', '/api/boards/:boardId/integrations/:intId/activities', function (req, res) {
|
||||
try {
|
||||
const paramBoardId = req.params.boardId;
|
||||
const paramIntId = req.params.intId;
|
||||
const newActivities = req.body.activities;
|
||||
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||
|
||||
Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
|
||||
{$addToSet: {activities: { $each: newActivities}}});
|
||||
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: Integrations.findOne({_id: paramIntId, boardId: paramBoardId}, { fields: {_id: 1, activities: 1}}),
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Delete integration
|
||||
JsonRoutes.add('DELETE', '/api/boards/:boardId/integrations/:intId', function (req, res) {
|
||||
try {
|
||||
const paramBoardId = req.params.boardId;
|
||||
const paramIntId = req.params.intId;
|
||||
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||
|
||||
Integrations.direct.remove({_id: paramIntId, boardId: paramBoardId});
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: {
|
||||
_id: paramIntId,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,89 @@
|
||||
## In Wekan code
|
||||
|
||||
wekan/models/lists.js at bottom:
|
||||
```
|
||||
JsonRoutes.add('GET', '/api/boards/:boardId/lists', function (req, res) {
|
||||
try {
|
||||
const paramBoardId = req.params.boardId;
|
||||
Authentication.checkBoardAccess( req.userId, paramBoardId);
|
||||
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: Lists.find({ boardId: paramBoardId, archived: false }).map(function (doc) {
|
||||
return {
|
||||
_id: doc._id,
|
||||
title: doc.title,
|
||||
};
|
||||
}),
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId', function (req, res) {
|
||||
try {
|
||||
const paramBoardId = req.params.boardId;
|
||||
const paramListId = req.params.listId;
|
||||
Authentication.checkBoardAccess( req.userId, paramBoardId);
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: Lists.findOne({ _id: paramListId, boardId: paramBoardId, archived: false }),
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
JsonRoutes.add('POST', '/api/boards/:boardId/lists', function (req, res) {
|
||||
try {
|
||||
Authentication.checkUserId( req.userId);
|
||||
const paramBoardId = req.params.boardId;
|
||||
const id = Lists.insert({
|
||||
title: req.body.title,
|
||||
boardId: paramBoardId,
|
||||
});
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: {
|
||||
_id: id,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId', function (req, res) {
|
||||
try {
|
||||
Authentication.checkUserId( req.userId);
|
||||
const paramBoardId = req.params.boardId;
|
||||
const paramListId = req.params.listId;
|
||||
Lists.remove({ _id: paramListId, boardId: paramBoardId });
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: {
|
||||
_id: paramListId,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
JsonRoutes.sendResult(res, {
|
||||
code: 200,
|
||||
data: error,
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,82 @@
|
||||
# Change Role at Web UI
|
||||
|
||||
BoardAdmin can change role of user at right sidebar / click avatar / select role like Admin, Normal, etc.
|
||||
|
||||
# Change Role with API
|
||||
|
||||
- https://wekan.github.io/api/
|
||||
- api.py at https://github.com/wekan/wekan
|
||||
- Login to API https://github.com/wekan/wekan/wiki/REST-API#example-call---as-json
|
||||
|
||||
# Disclaimer
|
||||
|
||||
This page tries to be as up to date as possible. If you see something wrong here, feel free to update the page and help other people like you, that greatly depends on our APIs. If you don't feel comfortable doing this kind of changes, please contact us by creating an [issue](https://github.com/wekan/wekan/issues/new).
|
||||
|
||||
## Add New Board Member with Role
|
||||
This example adds with normal role. See examples below for other roles.
|
||||
```
|
||||
curl -H "Authorization: Bearer a6DM_gOPRwBdynfXaGBaiiEwTiAuigR_Fj_81QmNpnf" \
|
||||
-H "Content-type:application/json" \
|
||||
-X POST \
|
||||
http://localhost:3000/api/boards/BOARD-ID-HERE/members/USER-ID-HERE/add \
|
||||
-d '{"action": "add","isAdmin": "false", "isNoComments":"false", "isCommentOnly": "false", "isWorker": "false" }'
|
||||
```
|
||||
like
|
||||
```
|
||||
/api/boards/{boardid}/members/{MEMBERID}/add Body{ "action" : add, "isadmin" : false ...........
|
||||
```
|
||||
|
||||
## Remove Member from Board
|
||||
```
|
||||
curl -H "Authorization: Bearer a6DM_gOPRwBdynfXaGBaiiEwTiAuigR_Fj_81QmNpnf" \
|
||||
-H "Content-type:application/json" \
|
||||
-X POST \
|
||||
http://localhost:3000/api/boards/BOARD-ID-HERE/members/USER-ID-HERE/remove \
|
||||
-d '{"action": "remove"}'
|
||||
```
|
||||
|
||||
# Change Role of Existing Board Member
|
||||
|
||||
## Admin
|
||||
```
|
||||
curl -H "Authorization: Bearer a6DM_gOPRwBdynfXaGBaiiEwTiAuigR_Fj_81QmNpnf" \
|
||||
-H "Content-type:application/json" \
|
||||
-X POST \
|
||||
http://localhost:3000/api/boards/BOARD-ID-HERE/members/USER-ID-HERE \
|
||||
-d '{"isAdmin": "true", "isNoComments":"false", "isCommentOnly": "false", "isWorker": "false"}'
|
||||
```
|
||||
## Normal
|
||||
```
|
||||
curl -H "Authorization: Bearer a6DM_gOPRwBdynfXaGBaiiEwTiAuigR_Fj_81QmNpnf" \
|
||||
-H "Content-type:application/json" \
|
||||
-X POST \
|
||||
http://localhost:3000/api/boards/BOARD-ID-HERE/members/USER-ID-HERE \
|
||||
-d '{"isAdmin": "false", "isNoComments":"false", "isCommentOnly": "false", "isWorker": "false"}'
|
||||
```
|
||||
## No Comments
|
||||
```
|
||||
curl -H "Authorization: Bearer a6DM_gOPRwBdynfXaGBaiiEwTiAuigR_Fj_81QmNpnf" \
|
||||
-H "Content-type:application/json" \
|
||||
-X POST \
|
||||
http://localhost:3000/api/boards/BOARD-ID-HERE/members/USER-ID-HERE \
|
||||
-d '{"isAdmin": "false", "isNoComments":"true", "isCommentOnly": "false", "isWorker": "false"}'
|
||||
```
|
||||
## Comment Only
|
||||
```
|
||||
curl -H "Authorization: Bearer a6DM_gOPRwBdynfXaGBaiiEwTiAuigR_Fj_81QmNpnf" \
|
||||
-H "Content-type:application/json" \
|
||||
-X POST \
|
||||
http://localhost:3000/api/boards/BOARD-ID-HERE/members/USER-ID-HERE \
|
||||
-d '{"isAdmin": "false", "isNoComments":"false", "isCommentOnly": "true", "isWorker": "false"}'
|
||||
```
|
||||
## Worker
|
||||
- Can: move cards, can assign himself to card, can comment.
|
||||
- Cannot: anything else including revoke card, change settings, create/delete/edit cards/tags and so on...
|
||||
```
|
||||
curl -H "Authorization: Bearer a6DM_gOPRwBdynfXaGBaiiEwTiAuigR_Fj_81QmNpnf" \
|
||||
-H "Content-type:application/json" \
|
||||
-X POST \
|
||||
http://localhost:3000/api/boards/BOARD-ID-HERE/members/USER-ID-HERE \
|
||||
-d '{"isAdmin": "false", "isNoComments":"false", "isCommentOnly": "false", "isWorker": "true"}'
|
||||
```
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# Disclaimer
|
||||
|
||||
This page tries to be as up to date as possible. If you see something wrong here, feel free to update the page and help other people like you, that greatly depends on our APIs. If you don't feel comfortable doing this kind of changes, please contact us by creating an [issue](https://github.com/wekan/wekan/issues/new).
|
||||
|
||||
# Retrieve cards by swimlane id
|
||||
|
||||
Please somebody add example by looking this:
|
||||
|
||||
[Issue](https://github.com/wekan/wekan/issues/1934) and [code](https://github.com/wekan/wekan/pull/1944/commits/be42b8d4cbdfa547ca019ab2dc9a590a115cc0e2). Also add to [Cards page](REST-API-Cards)
|
||||
|
||||
# Add Swimlane to Board
|
||||
|
||||
| API URL / Code Link | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| [/api/boards/:boardId/swimlanes](https://github.com/wekan/wekan/blob/main/models/swimlanes.js#L223) | `yes` | `POST` |
|
||||
|
||||
```shell
|
||||
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
|
||||
-H "Content-type:application/json" \
|
||||
-X POST \
|
||||
http://localhost:3000/api/boards/YRgy7Ku6uLFv2pYwZ/swimlanes \
|
||||
-d '{ "title": "Swimlane title text" }'
|
||||
```
|
||||
## Result example
|
||||
The new swimlane's ID is returned in the format:
|
||||
```json
|
||||
{
|
||||
"_id": "W9m9YxQKT6zZrKzRW"
|
||||
}
|
||||
```
|
||||
|
||||
# Delete a swimlane
|
||||
|
||||
| API URL / Code Link | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| [/api/boards/:boardId/swimlanes/:swimlaneId](https://github.com/wekan/wekan/blob/main/models/swimlanes.js#L257) | `yes` | `DELETE` |
|
||||
|
||||
```shell
|
||||
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
|
||||
-H "Content-type:application/json" \
|
||||
-X DELETE \
|
||||
http://localhost:3000/api/boards/YRgy7Ku6uLFv2pYwZ/lists/PgTuf6sFJsaxto5dC/cards/ssrNX9CvXvPxuC5DE
|
||||
```
|
||||
## Result example
|
||||
The swimlane's ID is returned in the format:
|
||||
```json
|
||||
{
|
||||
"_id": "W9m9YxQKT6zZrKzRW"
|
||||
}
|
||||
```
|
||||
|
||||
# In Wekan code
|
||||
|
||||
If you believe that code is the best documentation, be our guest: [models/cards.js](https://github.com/wekan/wekan/blob/main/models/swimlanes.js "Swimlane API code")
|
||||
@@ -0,0 +1,335 @@
|
||||
# Disclaimer
|
||||
|
||||
This page tries to be as up to date as possible. If you see something wrong here, feel free to update the page and help other people like you, that greatly depends on our APIs. If you don't feel comfortable doing this kind of changes, please contact us by creating an [issue](https://github.com/wekan/wekan/issues/new).
|
||||
|
||||
# User Register
|
||||
| URL | Requires Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| `/users/register` | `no` | `POST` |
|
||||
|
||||
## Payload
|
||||
|
||||
| Argument | Example | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `username` | `myusername` | Required | Your username |
|
||||
| `password` | `my$up3erP@ssw0rd` | Required | Your password |
|
||||
| `email` | `my@email.com` | Required | Your email |
|
||||
|
||||
* Notes:
|
||||
* **You will need to provide the `token` for any of the authenticated methods.**
|
||||
|
||||
## Example Call - As Form Data
|
||||
```bash
|
||||
curl http://localhost:3000/users/register \
|
||||
-d "username=myusername&password=mypassword&email=my@email.com"
|
||||
```
|
||||
|
||||
|
||||
## Example Call - As JSON
|
||||
```bash
|
||||
curl -H "Content-type:application/json" \
|
||||
http://localhost:3000/users/register \
|
||||
-d '{ "username": "myusername", "password": "mypassword", "email": "my@email.com" }'
|
||||
```
|
||||
|
||||
|
||||
## Result
|
||||
```json
|
||||
{
|
||||
"id": "user id",
|
||||
"token": "string",
|
||||
"tokenExpires": "ISO encoded date string"
|
||||
}
|
||||
```
|
||||
|
||||
## Result example
|
||||
```json
|
||||
{
|
||||
"id": "XQMZgynx9M79qTtQc",
|
||||
"token": "ExMp2s9ML1JNp_l11sIfINPT3wykZ1SsVwg-cnxKdc8",
|
||||
"tokenExpires": "2017-12-15T00:47:26.303Z"
|
||||
}
|
||||
```
|
||||
|
||||
# User Create
|
||||
| URL | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| `/api/users` | `yes` | `POST` |
|
||||
|
||||
## Payload
|
||||
|
||||
| Argument | Example | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `username` | `myusername` | Required | Your username |
|
||||
| `password` | `my$up3erP@ssw0rd` | Required | Your password |
|
||||
| `email` | `my@email.com` | Required | Your email |
|
||||
|
||||
* Notes:
|
||||
* **You will need to provide the `token` for any of the authenticated methods.**
|
||||
|
||||
## Example Call - As Form Data
|
||||
```bash
|
||||
curl -H "Authorization: Bearer a6DM_gOPRwBdynfXaGBaiiEwTiAuigR_Fj_81QmNpnf" \
|
||||
-X POST \
|
||||
http://localhost:3000/api/users \
|
||||
-d "username=myusername&password=mypassword&email=my@email.com"
|
||||
```
|
||||
|
||||
## Example Call - As JSON
|
||||
```bash
|
||||
curl -H "Authorization: Bearer a6DM_gOPRwBdynfXaGBaiiEwTiAuigR_Fj_81QmNpnf" \
|
||||
-H "Content-type:application/json" \
|
||||
-X POST \
|
||||
http://localhost:3000/api/users \
|
||||
-d '{ "username": "myusername", "password": "mypassword", "email": "my@email.com" }'
|
||||
```
|
||||
|
||||
## Example of all steps of create user
|
||||
|
||||
1) Login
|
||||
|
||||
```
|
||||
curl http://example.com/users/login \
|
||||
-d "username=YOUR-USERNAME-HERE&password=YOUR-PASSWORD-HERE"
|
||||
```
|
||||
|
||||
As response you get your id and token:
|
||||
|
||||
```
|
||||
"id":"YOUR-ID-HERE","token":"YOUR-TOKEN-HERE","tokenExpires":"2017-12-23T21:07:10.395Z"}
|
||||
```
|
||||
|
||||
2) Create user. Works both when serf-register enabled and disabled.
|
||||
|
||||
```
|
||||
curl -H "Authorization: Bearer YOUR-TOKEN-HERE" \
|
||||
-H "Content-type:application/json" \
|
||||
-X POST \
|
||||
http://example.com/api/users \
|
||||
-d '{ "username": "tester", "password": "tester", "email": "tester@example.com", "fromAdmin": "true" }'
|
||||
```
|
||||
|
||||
As reply you get new user's id.
|
||||
|
||||
```
|
||||
{"id":"NEW-USER-ID-HERE"}
|
||||
```
|
||||
|
||||
3) You can get user details with your new user's id:
|
||||
|
||||
```
|
||||
curl -H "Authorization: Bearer YOUR-TOKEN-HERE" \
|
||||
http://example.com/api/users/NEW-USER-ID-HERE
|
||||
```
|
||||
|
||||
## Result
|
||||
|
||||
Returns the id of the created user.
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"_id": "user id"
|
||||
}
|
||||
```
|
||||
|
||||
## Result example
|
||||
```json
|
||||
{
|
||||
"_id": "EnhMbvxh65Hr7YvtG"
|
||||
}
|
||||
```
|
||||
|
||||
# User Delete
|
||||
|
||||
> IMPORTANT : Should not be used as long as [this bug](https://github.com/wekan/wekan/issues/1289) exists.
|
||||
|
||||
| URL | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| `/api/users/:id` | `yes` | `DELETE` |
|
||||
|
||||
## Parameters
|
||||
| Argument | Example | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `id` | `BsNr28znDkG8aeo7W` | Required | The id of the user to delete. |
|
||||
|
||||
## Example Call
|
||||
```bash
|
||||
curl -H "Authorization: Bearer a6DM_gOPRwBdynfXaGBaiiEwTiAuigR_Fj_81QmNpnf" \
|
||||
-X DELETE \
|
||||
http://localhost:3000/api/users/EnhMbvxh65Hr7YvtG
|
||||
```
|
||||
|
||||
## Example Result
|
||||
|
||||
Returns the id of the deleted user.
|
||||
|
||||
```json
|
||||
{
|
||||
"_id": "EnhMbvxh65Hr7YvtG"
|
||||
}
|
||||
```
|
||||
|
||||
# User Information
|
||||
Retrieves information about a user.
|
||||
|
||||
| URL | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| `/api/users/:id` | `yes` | `GET` |
|
||||
|
||||
* Notes:
|
||||
* **You will need to provide the `token` for any of the authenticated methods.**
|
||||
* **Only the admin user (the first user) can call the REST API.**
|
||||
|
||||
## Example Call
|
||||
```bash
|
||||
curl -H "Authorization: Bearer a6DM_gOPRwBdynfXaGBaiiEwTiAuigR_Fj_81QmNpnf" \
|
||||
http://localhost:3000/api/users/XQMZgynx9M79qTtQc
|
||||
```
|
||||
|
||||
## Result example
|
||||
```json
|
||||
{
|
||||
"_id": "XQMZgynx9M79qTtQc",
|
||||
"createdAt": "2017-09-13T06:45:53.127Z",
|
||||
"services": {
|
||||
"password": {
|
||||
"bcrypt": "$2a$10$CRZrpT4x.VpG2FdJxR3rN.9m0NbQb0OPsSPBDAZukggxrskMtWA8."
|
||||
},
|
||||
"email": {
|
||||
"verificationTokens": [
|
||||
{
|
||||
"token": "8rzwpq_So2PVYHVSfrcc5f5QZnuV2wEtu7QRQGwOJx8",
|
||||
"address": "my@email.com",
|
||||
"when": "2017-09-13T06:45:53.157Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
"resume": {
|
||||
"loginTokens": [
|
||||
{
|
||||
"when": "2017-09-13T06:45:53.265Z",
|
||||
"hashedToken": "CY/PWeDa3fAkl+k94+GWzCtpB5nPcVxLzzzjXs4kI3A="
|
||||
},
|
||||
{
|
||||
"when": "2017-09-16T06:06:19.741Z",
|
||||
"hashedToken": "74MQNXfsgjkItx/gpgPb29Y0MSNAvBrsnSGQmr4YGvQ="
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"username": "john",
|
||||
"emails": [
|
||||
{
|
||||
"address": "my@email.com",
|
||||
"verified": false
|
||||
}
|
||||
],
|
||||
"isAdmin": true,
|
||||
"profile": {}
|
||||
}
|
||||
```
|
||||
# User List
|
||||
Retrieves the user list.
|
||||
|
||||
| URL | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| `/api/users` | `yes` | `GET` |
|
||||
|
||||
* Notes:
|
||||
* **You will need to provide the `token` for any of the authenticated methods.**
|
||||
* **Only the admin user (the first user) can call the REST API.**
|
||||
|
||||
## Example Call
|
||||
```bash
|
||||
curl -H "Authorization: Bearer cwUZ3ZsTaE6ni2R3ppSkYd-KrDvxsLcBIkSVfOCfIkA" \
|
||||
http://localhost:3000/api/users
|
||||
```
|
||||
|
||||
## Result
|
||||
```json
|
||||
[
|
||||
{
|
||||
"_id": "user id",
|
||||
"username": "string"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Result example
|
||||
```json
|
||||
[
|
||||
{
|
||||
"_id": "XQMZgynx9M79qTtQc",
|
||||
"username": "admin"
|
||||
},
|
||||
{
|
||||
"_id": "vy4WYj7k7NBhf3AFc",
|
||||
"username": "john"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
# User Logged-in
|
||||
Retrieves information about a logged-in user with his auth token.
|
||||
|
||||
| URL | Requires Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| `/api/user` | `yes` | `GET` |
|
||||
|
||||
* Notes:
|
||||
* **You will need to provide the `token` for any of the authenticated methods.**
|
||||
|
||||
## Example Call
|
||||
```bash
|
||||
curl -H "Authorization: Bearer a6DM_gOPRwBdynfXaGBaiiEwTiAuigR_Fj_81QmNpnf" \
|
||||
http://localhost:3000/api/user
|
||||
```
|
||||
|
||||
## Result example
|
||||
```json
|
||||
{
|
||||
"_id": "vy4WYj7k7NBhf3AFc",
|
||||
"createdAt": "2017-09-16T05:51:30.339Z",
|
||||
"username": "john",
|
||||
"emails": [
|
||||
{
|
||||
"address": "me@mail.com",
|
||||
"verified": false
|
||||
}
|
||||
],
|
||||
"profile": {}
|
||||
}
|
||||
```
|
||||
|
||||
# Disable a user (the user is not allowed to login and his login tokens are purged)
|
||||
|
||||
| URL | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| `/api/users/:id` | `yes` | `PUT` |
|
||||
|
||||
```shell
|
||||
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
|
||||
-H "Content-type:application/json" \
|
||||
-X PUT \
|
||||
http://localhost:3000/api/users/ztKvBTzCqmyJ77on8 \
|
||||
-d '{ "action": "disableLogin" }'
|
||||
```
|
||||
|
||||
# Enable a user
|
||||
|
||||
| URL | Requires Admin Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| `/api/users/:id` | `yes` | `PUT` |
|
||||
|
||||
```shell
|
||||
curl -H "Authorization: Bearer t7iYB86mXoLfP_XsMegxF41oKT7iiA9lDYiKVtXcctl" \
|
||||
-H "Content-type:application/json" \
|
||||
-X PUT \
|
||||
http://localhost:3000/api/users/ztKvBTzCqmyJ77on8 \
|
||||
-d '{ "action": "enableLogin" }'
|
||||
```
|
||||
|
||||
# In Wekan code
|
||||
|
||||
If you believe that code is the best documentation, be our guest: [models/cards.js](https://github.com/wekan/wekan/blob/main/models/users.js "User API code")
|
||||
@@ -0,0 +1,126 @@
|
||||
REST API is not complete yet, please add missing functionality with pull requests to devel branch.
|
||||
|
||||
If you are in a hurry, you can use these to have more functionality:
|
||||
* https://restheart.org
|
||||
* http://vulcanjs.org
|
||||
|
||||
For workflows see [If-this-then-that issue](https://github.com/wekan/wekan/issues/1160) than mentions Huginn, Flogo etc.
|
||||
|
||||
# Wekan REST API
|
||||
|
||||
The REST API allows you to control and extend Wekan with ease.
|
||||
|
||||
If you are an end-user and not a dev or a tester, [create an issue](https://github.com/wekan/wekan/issues/new) to request new APIs.
|
||||
|
||||
> All API calls in the documentation are made using `curl`. However, you are free to use Java / Python / PHP / Golang / Ruby / Swift / Objective-C / Rust / Scala / C# or any other programming languages.
|
||||
|
||||
## Production Security Concerns
|
||||
When calling a production Wekan server, ensure it is running via HTTPS and has a valid SSL Certificate. The login method requires you to post your username and password in plaintext, which is why we highly suggest only calling the REST login api over HTTPS. Also, few things to note:
|
||||
|
||||
* Only call via HTTPS
|
||||
* Implement a timed authorization token expiration strategy
|
||||
* Ensure the calling user only has permissions for what they are calling and no more
|
||||
|
||||
# Summary
|
||||
|
||||
### Authentication
|
||||
| HTTP Method | Url | Short Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `POST` | `/users/login` | [Authenticate with the REST API.](#login) |
|
||||
|
||||
### Users
|
||||
| HTTP Method | Url | Short Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `POST` | `/users/register` | [Register a new user.](REST-API-User#user-register) |
|
||||
| `POST` | `/api/users` | [Create a new user.](REST-API-User#user-create) |
|
||||
| `PUT` | `/api/users/:id` | [Disable an existing user.](REST-API-User#disable-a-user-the-user-is-not-allowed-to-login-and-his-login-tokens-are-purged) |
|
||||
| `PUT` | `/api/users/:id` | [Enable an existing user.](REST-API-User#enable-a-user) |
|
||||
| `PUT` | `/api/users/:id` | [Admin takes the ownership.](REST-API-User#the-admin-takes-the-ownership-of-all-boards-of-the-user-archived-and-not-archived-where-the-user-is-admin-on) |
|
||||
| `DELETE` | `/api/users/:id` | [Delete an existing user.](REST-API-User#user-delete) ([Warning](https://github.com/wekan/wekan/issues/1289))|
|
||||
| `GET` | `/api/users/:id` | [Gets a user's information.](REST-API-User#user-information) |
|
||||
| `GET` | `/api/users` | [All of the users.](REST-API-User#user-list) |
|
||||
| `GET` | `/api/user` | [Gets a logged-in user.](REST-API-User#user-logged-in) |
|
||||
### Cards
|
||||
| HTTP Method | Url | Short Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `POST` | `/api/boards/:boardId/lists/:listId/cards` | [Add a card to a list, board, and swimlane.](REST-API-Cards#add-card-to-list-board-swimlane) |
|
||||
| `PUT` | `/api/boards/:boardId/lists/:fromListId/cards/:cardId` | [Update a card.](REST-API-Cards#update-a-card) |
|
||||
| `DELETE` | `/api/boards/:boardId/lists/:listId/cards/:cardId` | [Delete a card.](REST-API-Cards#update-a-card) |
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Login
|
||||
| URL | Requires Auth | HTTP Method |
|
||||
| :--- | :--- | :--- |
|
||||
| `/users/login` | `no` | `POST` |
|
||||
|
||||
## Payload
|
||||
|
||||
### Authentication with username
|
||||
| Argument | Example | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `username` | `myusername` | Required | Your username |
|
||||
| `password` | `my$up3erP@ssw0rd` | Required | Your password |
|
||||
|
||||
### Authentication with email
|
||||
| Argument | Example | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `email` | `my@email.com` | Required | Your email |
|
||||
| `password` | `my$up3erP@ssw0rd` | Required | Your password |
|
||||
|
||||
* Notes:
|
||||
* **You will need to provide the `token` for any of the authenticated methods.**
|
||||
|
||||
## Example Call - As Form Data
|
||||
|
||||
DOES NOT WORK ! Please use As JSON example below !
|
||||
https://github.com/wekan/wekan/issues/4807
|
||||
|
||||
```bash
|
||||
curl http://localhost:3000/users/login \
|
||||
-d "username=myusername&password=mypassword"
|
||||
```
|
||||
|
||||
```bash
|
||||
curl http://localhost:3000/users/login \
|
||||
-d "email=my@email.com&password=mypassword"
|
||||
```
|
||||
|
||||
|
||||
## Example Call - As JSON
|
||||
|
||||
THIS WORKS !! Alternatively, look at api.py example at https://github.com/wekan/wekan
|
||||
|
||||
NOTE: Username and password is case sensitive. So type BIG and small letters correctly.
|
||||
|
||||
```bash
|
||||
curl -H "Content-type:application/json" \
|
||||
http://localhost:3000/users/login \
|
||||
-d '{ "username": "myusername", "password": "mypassword" }'
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -H "Content-type:application/json" \
|
||||
http://localhost:3000/users/login \
|
||||
-d '{ "email": "my@email.com", "password": "mypassword" }'
|
||||
```
|
||||
|
||||
|
||||
## Result
|
||||
```json
|
||||
{
|
||||
"id": "user id",
|
||||
"token": "string",
|
||||
"tokenExpires": "ISO encoded date string"
|
||||
}
|
||||
```
|
||||
|
||||
## Result example
|
||||
```json
|
||||
{
|
||||
"id": "XQMZgynx9M79qTtQc",
|
||||
"token": "ExMp2s9ML1JNp_l11sIfINPT3wykZ1SsVwg-cnxKdc8",
|
||||
"tokenExpires": "2017-12-15T00:47:26.303Z"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
Admin Panel / People / Click at right side of usename `...` button that is User Settings / Impersonate user.
|
||||
|
||||
You will be switched to logged in as that user.
|
||||
|
||||
To switch back to your original user, at your webbrowser click `Reload open webpage` round arrow button.
|
||||
|
||||
This feature was added to Wekan v4.39 at https://github.com/wekan/wekan/pull/3280
|
||||
|
||||
@@ -0,0 +1,784 @@
|
||||
[Sandstorm](Sandstorm) - [Sandstorm Backup](Export-from-Wekan-Sandstorm-grain-.zip-file)
|
||||
|
||||
# Upcoming
|
||||
|
||||
[Transferring to Minio and SQLite](https://github.com/wekan/minio-metadata)
|
||||
|
||||
# Backup Docker
|
||||
|
||||
[Also see: Upgrading Synology with Wekan quay images](https://github.com/wekan/wekan/issues/3874#issuecomment-867526249)
|
||||
|
||||
Note: Do not run `docker-compose down` without verifying your docker-compose file, it does not delete the data by default but caution is advised. Refer to https://docs.docker.com/compose/reference/down/.
|
||||
|
||||
[docker-compose.yml](https://raw.githubusercontent.com/wekan/wekan/master/docker-compose.yml)
|
||||
|
||||
This presumes your Wekan Docker is currently running with:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
Backup to directory dump:
|
||||
```bash
|
||||
docker stop wekan-app
|
||||
docker exec wekan-db rm -rf /data/dump
|
||||
docker exec wekan-db mongodump -o /data/dump
|
||||
docker cp wekan-db:/data/dump .
|
||||
docker start wekan-app
|
||||
```
|
||||
Copy dump directory to other server or to your backup.
|
||||
|
||||
# Restore Docker
|
||||
```bash
|
||||
docker stop wekan-app
|
||||
docker exec wekan-db rm -rf /data/dump
|
||||
docker cp dump wekan-db:/data/
|
||||
docker exec wekan-db mongorestore --drop --dir=/data/dump
|
||||
docker start wekan-app
|
||||
```
|
||||
# Upgrade Docker Wekan version
|
||||
|
||||
## Newest info
|
||||
|
||||
https://github.com/wekan/wekan/discussions/5367
|
||||
|
||||
## Old info
|
||||
|
||||
Note: Do not run `docker-compose down` without verifying your docker-compose file, it does not delete the data by default but caution is advised. Refer to https://docs.docker.com/compose/reference/down/.
|
||||
```bash
|
||||
docker-compose stop
|
||||
docker rm wekan-app
|
||||
```
|
||||
a) For example, if you in docker-compose.yml use `image: wekanteam/wekan` or `image: quay.io/wekan/wekan` for latest development version
|
||||
|
||||
b) Or in docker-compose.yml change version tag, or use version tag like `image: wekanteam/wekan:v5.50` or `image: quay.io/wekan/wekan:v5.50`
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
# Backup Wekan Snap to directory dump
|
||||
```bash
|
||||
sudo snap stop wekan.wekan
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
|
||||
export PATH="/snap/wekan/current/bin:$PATH"
|
||||
mongodump --port 27019
|
||||
sudo snap get wekan > snap-settings.sh
|
||||
```
|
||||
NOTE for Arch Linux: Use this path instead. [Source](https://github.com/wekan/wekan/issues/3941).
|
||||
```bash
|
||||
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH-}:/var/lib/snapd/snap/wekan/current/lib/x86_64-linux-gnu
|
||||
```
|
||||
username is your /home/username
|
||||
```bash
|
||||
sudo chown username:username snap-settings.sh
|
||||
sudo snap start wekan.wekan
|
||||
```
|
||||
Modify snap-settings.sh so that it has commands like:
|
||||
```bash
|
||||
sudo snap set wekan root-url='http://localhost'
|
||||
```
|
||||
Set snap-settings.sh executeable:
|
||||
```bash
|
||||
chmod +x snap-settings.sh
|
||||
```
|
||||
|
||||
# Restore Wekan Snap
|
||||
```bash
|
||||
sudo snap stop wekan.wekan
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
|
||||
export PATH="$PATH:/snap/wekan/current/bin"
|
||||
mongorestore --drop --port 27019
|
||||
sudo snap start wekan.wekan
|
||||
./snap-settings.sh
|
||||
```
|
||||
# Upgrade Snap manually immediately (usually it updates automatically)
|
||||
|
||||
```bash
|
||||
sudo snap refresh
|
||||
```
|
||||
|
||||
# Backup Wekan Gantt GPLv2 Snap to directory dump
|
||||
```bash
|
||||
sudo snap stop wekan-gantt-gpl.wekan
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan-gantt-gpl/current/lib/x86_64-linux-gnu
|
||||
export PATH="$PATH:/snap/wekan-gantt-gpl/current/bin"
|
||||
mongodump --port 27019
|
||||
sudo snap get wekan-gantt-gpl > snap-settings.sh
|
||||
```
|
||||
username is your /home/username
|
||||
```bash
|
||||
sudo chown username:username snap-settings.sh
|
||||
sudo snap start wekan-gantt-gpl.wekan
|
||||
```
|
||||
Modify snap-settings.sh so that it has command like:
|
||||
```bash
|
||||
sudo snap set wekan-gantt-gpl root-url='http://localhost'
|
||||
```
|
||||
Set snap-settings.sh executeable:
|
||||
```bash
|
||||
chmod +x snap-settings.sh
|
||||
```
|
||||
|
||||
# Restore Wekan Gantt GPLv2 Snap
|
||||
```bash
|
||||
sudo snap stop wekan-gantt-gpl.wekan
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan-gantt-gpl/current/lib/x86_64-linux-gnu
|
||||
export PATH="$PATH:/snap/wekan-gantt-gpl/current/bin"
|
||||
mongorestore --drop --port 27019
|
||||
sudo snap start wekan-gantt-gpl.wekan
|
||||
./snap-settings.sh
|
||||
```
|
||||
|
||||
# DBGate, Open Source MongoDB GUI
|
||||
|
||||
https://dbgate.org - https://github.com/dbgate/dbgate
|
||||
|
||||
# Using nosqlbooster closed source MongoDB GUI with Wekan Snap to edit MongoDB database
|
||||
|
||||
https://nosqlbooster.com/downloads
|
||||
|
||||
### At server where Wekan Snap is installed, MongoDB is running at localhost port 27019
|
||||
|
||||
<img src="https://wekan.github.io/nosqlbooster-basic-connection.png" width="60%" alt="Wekan logo" />
|
||||
|
||||
### You can tunnel via SSH to server, using password auth or private key auth dropdown selection
|
||||
|
||||
<img src="https://wekan.github.io/nosqlbooster-ssh-tunnel.png" width="60%" alt="Wekan logo" />
|
||||
|
||||
# Scheduled backups to local or remote server
|
||||
|
||||
For below scheduled backup scripts, no info from above of this wiki page is required. Backup scripts below have the required settings.
|
||||
|
||||
This does backup of [Wekan+RocketChat snap databases](OAuth2) and php website etc.
|
||||
|
||||
If you need to backup some remote server or cloud, you can use scp, or read [rclone docs](https://rclone.org/docs/) about how to configure saving to some other remote server or cloud.
|
||||
|
||||
The following .sh bash scripts are added as root user to your `/root/` directory.
|
||||
|
||||
Backups are created to subdirectories of `/root/backups/`.
|
||||
|
||||
Cron is used to schedule backups, for example once a day.
|
||||
|
||||
1. To add bash scripts, you change to root user with this command, and your sudo or root password.
|
||||
```bash
|
||||
sudo su
|
||||
```
|
||||
2. Use nano editor for editing cron. If you don't have it installed, type:
|
||||
```bash
|
||||
apt install nano
|
||||
```
|
||||
3. Then we set text editor to be nano. Otherwise it probably uses vi, that is harder to use.
|
||||
```bash
|
||||
export EDITOR=nano
|
||||
```
|
||||
4. Now we start editing cron scheduler.
|
||||
```bash
|
||||
crontab -e
|
||||
```
|
||||
For more info how to make cron time, see https://crontab.guru
|
||||
|
||||
In this example, we set backups every day at 04:00, then it runs backup.sh script, and saves output of the backup commands to the bottom of textfile backup.log.txt
|
||||
```bash
|
||||
# m h dom mon dow command
|
||||
0 4 * * * /root/backup.sh >> /root/backup.log.txt 2>&1
|
||||
```
|
||||
- For changing to `/root` directory, type: `cd /root`
|
||||
- for editing backup.sh file, type: `nano backup.sh`
|
||||
- For saving in nano, press Ctrl-o Enter
|
||||
- For exiting nano, press Ctrl-x Enter
|
||||
- Set every .sh file as executeable, for example: `chmod +x backup.sh`
|
||||
|
||||
This is content of `backup.sh` script. It runs all the other backup scripts.
|
||||
If you do not need to backup rocketchat or website, or do not need to use rclone,
|
||||
you don't need to add those command lines at all.
|
||||
```bash
|
||||
cd /root
|
||||
./backup-wekan.sh
|
||||
./backup-rocketchat.sh
|
||||
./backup-website.sh
|
||||
rclone move backups cloudname:backup.example.com
|
||||
```
|
||||
More about rclone:
|
||||
|
||||
/root/rclone-ls-all.sh , shows directory contests at cloud:
|
||||
```bash
|
||||
rclone lsd cloudname:
|
||||
```
|
||||
In this example, cron does run backup scripts as root.
|
||||
This is if you edit cron with command `crontab -e` as root user,
|
||||
so it edits the cron of root user.
|
||||
|
||||
If mongodump command works as normal user for you, you could instead
|
||||
run backups as normal user, by exiting root user with `exit` and
|
||||
then as normal user editing cron with `crontab -e`.
|
||||
You can also list current cron with command `crontab -l`.
|
||||
|
||||
If you like to backup Wekan snap settings with this command, then it
|
||||
only works with sudo at front, or as a root user without sudo at front.
|
||||
```bash
|
||||
sudo snap get wekan > snap-settings.txt
|
||||
```
|
||||
|
||||
This below is backup script for backing up Wekan.
|
||||
|
||||
/root/backup-wekan.sh
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
makeDump()
|
||||
{
|
||||
|
||||
# Backups will be created below this directory.
|
||||
backupdir="/root/backups/wekan"
|
||||
|
||||
# Gets the version of the snap.
|
||||
version=$(snap list | grep wekan | awk -F ' ' '{print $3}')
|
||||
|
||||
# Gets current time to variable "now"
|
||||
now=$(date +'%Y-%m-%d_%H.%M.%S')
|
||||
|
||||
# Creates new backup directory like BACKUPDIR/BACKUPVERSIO-TIMENOW
|
||||
mkdir -p $backupdir/$version-$now
|
||||
|
||||
# Targets the dump file.
|
||||
#dump=$"/snap/wekan/$version/bin/mongodump"
|
||||
|
||||
# Changes to backup directory
|
||||
cd $backupdir/$version-$now
|
||||
|
||||
# Backup Caddy settings
|
||||
snap get wekan > snap-settings.txt
|
||||
|
||||
# Show text that database backup is in progress
|
||||
printf "\nThe database backup is in progress.\n\n"
|
||||
|
||||
# Backup to current directory, creates subdirectory called "dump"
|
||||
# with database dump files
|
||||
mongodump --port 27019
|
||||
|
||||
# Change diretory (=cd) to parent directory
|
||||
cd ..
|
||||
|
||||
# Show text "Makes the tar.gz archive file"
|
||||
printf "\nMakes the tar.gz archive file.\n"
|
||||
|
||||
# Creates tar.gz archive file. This works similarly like creating .zip file.
|
||||
tar -zcvf $version-$now.tar.gz $version-$now
|
||||
|
||||
# Delete temporary files that have already been
|
||||
# compressed to above tar.gz file
|
||||
rm -rf $version-$now
|
||||
|
||||
# Shows text "Backup done."
|
||||
printf "\nBackup done.\n"
|
||||
|
||||
# Show where backup archive file is.
|
||||
echo "Backup is archived to .tar.gz file at $backupdir/${version}-${now}.tar.gz"
|
||||
}
|
||||
|
||||
# Checks is the user is sudo/root
|
||||
if [ "$UID" -ne "0" ]
|
||||
then
|
||||
echo "This program must be launched with sudo/root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Starts
|
||||
makeDump
|
||||
```bash
|
||||
/root/backup-rocketchat.sh
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
makeDump()
|
||||
{
|
||||
|
||||
backupdir="/root/backups/rocketchat"
|
||||
|
||||
# Gets the version of the snap.
|
||||
version=$(snap list | grep wekan | awk -F ' ' '{print $3}')
|
||||
|
||||
# Prepares.
|
||||
now=$(date +'%Y-%m-%d_%H.%M.%S')
|
||||
mkdir -p $backupdir/$version-$now
|
||||
|
||||
# Targets the dump file.
|
||||
dump=$"/snap/wekan/$version/bin/mongodump"
|
||||
|
||||
# Makes the backup.
|
||||
cd $backupdir/$version-$now
|
||||
printf "\nThe database backup is in progress.\n\n"
|
||||
$dump --port 27017
|
||||
|
||||
# Makes the tar.gz file.
|
||||
cd ..
|
||||
printf "\nMakes the tar.gz file.\n"
|
||||
tar -zcvf $version-$now.tar.gz $version-$now
|
||||
|
||||
# Cleanups
|
||||
rm -rf $version-$now
|
||||
|
||||
# End.
|
||||
printf "\nBackup done.\n"
|
||||
echo "Backup is archived to .tar.gz file at $backupdir/${version}-${now}.tar.gz"
|
||||
}
|
||||
|
||||
# Checks is the user is sudo/root
|
||||
if [ "$UID" -ne "0" ]
|
||||
then
|
||||
echo "This program must be launched with sudo/root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Starts
|
||||
makeDump
|
||||
```
|
||||
/root/backup-website.sh
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
makeDump()
|
||||
{
|
||||
|
||||
backupdir="/root/backups/example.com"
|
||||
|
||||
# Gets the version of the snap.
|
||||
version=$(snap list | grep wekan | awk -F ' ' '{print $3}')
|
||||
|
||||
# Prepares.
|
||||
now=$(date +'%Y-%m-%d_%H.%M.%S')
|
||||
mkdir -p $backupdir/$version-$now
|
||||
|
||||
# Makes the backup.
|
||||
cd $backupdir/$version-$now
|
||||
printf "\nThe file backup is in progress.\n\n"
|
||||
|
||||
# Makes the tar.gz file.
|
||||
cd ..
|
||||
printf "\nMakes the tar.gz file.\n"
|
||||
cp -pR /var/snap/wekan/common/example.com $version-$now
|
||||
tar -zcvf $version-$now.tar.gz $version-$now
|
||||
|
||||
# Cleanups
|
||||
rm -rf $version-$now
|
||||
|
||||
# End.
|
||||
printf "\nBackup done.\n"
|
||||
echo "Backup is archived to .tar.gz file at $backupdir/${version}-${now}.tar.gz"
|
||||
}
|
||||
|
||||
# Checks is the user is sudo/root
|
||||
if [ "$UID" -ne "0" ]
|
||||
then
|
||||
echo "This program must be launched with sudo/root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Starts
|
||||
makeDump
|
||||
```
|
||||
/var/snap/wekan/common/Caddyfile
|
||||
```bash
|
||||
chat.example.com {
|
||||
proxy / localhost:3000 {
|
||||
websocket
|
||||
transparent
|
||||
}
|
||||
}
|
||||
|
||||
https://boards.example.com {
|
||||
proxy / localhost:3001 {
|
||||
websocket
|
||||
transparent
|
||||
}
|
||||
}
|
||||
|
||||
example.com {
|
||||
root /var/snap/wekan/common/example.com
|
||||
fastcgi / /var/run/php/php7.0-fpm.sock php
|
||||
}
|
||||
|
||||
matomo.example.com {
|
||||
root /var/snap/wekan/common/matomo.example.com
|
||||
fastcgi / /var/run/php/php7.0-fpm.sock php
|
||||
}
|
||||
|
||||
# Example CloudFlare free wildcard SSL Origin Certificate, there is example.com.pem at certificates directory with private key at to and cert at bottom.
|
||||
http://example.com https://example.com {
|
||||
tls {
|
||||
load /var/snap/wekan/common/certificates
|
||||
alpn http/1.1
|
||||
}
|
||||
root /var/snap/wekan/common/example.com
|
||||
browse
|
||||
}
|
||||
|
||||
static.example.com {
|
||||
root /var/snap/wekan/common/static.example.com
|
||||
}
|
||||
```
|
||||
|
||||
## Related talk about MongoDB backup
|
||||
|
||||
Related talk, search for "mongodb" this page:
|
||||
https://fosdem.org/2020/schedule/events/
|
||||
|
||||
There is this:
|
||||
|
||||
Percona Backup for MongoDB: Status and Plans
|
||||
|
||||
Open Source solution for consistent backups of multi-shard MongoDB
|
||||
|
||||
- [Slides](https://fosdem.org/2020/schedule/event/perconamongodb/attachments/slides/3768/export/events/attachments/perconamongodb/slides/3768/Percona_Backup_for_MongoDB.pdf)
|
||||
- [Video webm](https://video.fosdem.org/2020/UD2.119/perconamongodb.webm)
|
||||
- [Same Video mp4](https://video.fosdem.org/2020/UD2.119/perconamongodb.mp4)
|
||||
|
||||
## Related Sandstorm issue
|
||||
|
||||
[Creating a backup while the grain is running could cause corruption](https://github.com/sandstorm-io/sandstorm/issues/3186).
|
||||
|
||||
## Combining old and new Wekan version data
|
||||
|
||||
Note: Do mongodump/mongorestore only when Wekan is stopped: wekan.wekan (Snap) or wekan-app (Docker).
|
||||
|
||||
1. From new Wekan export all boards to Wekan JSON.
|
||||
2. Backup new Wekan with mongodump.
|
||||
3. Backup old Wekan with mongodump.
|
||||
4. Restore old Wekan data to new Wekan with mongorestore.
|
||||
5. Restore new Wekan JSON exported boards by importing them.
|
||||
|
||||
## Rescuing board that does not load
|
||||
|
||||
Wekan web UI Import/Export JSON does not have all content currently. To upgrade from old Wekan version, use mongodump/mongorestore to newest Wekan, like described below.
|
||||
|
||||
To import big JSON file, on Linux you can use xclip to copy textfile to clipboard:
|
||||
```bash
|
||||
sudo apt-get install xclip
|
||||
cat board.json | xclip -se c
|
||||
```
|
||||
Then paste to webbrowser Wekan Add Board / Import / From previous export.
|
||||
|
||||
You can [save all MongoDB database content as JSON files](Export-from-Wekan-Sandstorm-grain-.zip-file). Files are base64 encoded in JSON files.
|
||||
|
||||
Export board to Wekan JSON, and import as Wekan JSON can make some part of board to load, but you should check is some data missing.
|
||||
|
||||
With Wekan Snap, you can use [nosqlbooster GUI](https://nosqlbooster.com/downloads) to login through SSH to Wekan server localhost port 27019 and browse data.
|
||||
|
||||
You could use [daff](https://github.com/paulfitz/daff) to compare tables.
|
||||
|
||||
## Using Snap Mongo commands on your bash CLI
|
||||
|
||||
Add to your `~/.bashrc`
|
||||
```bash
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
|
||||
export PATH="$PATH:/snap/wekan/current/bin"
|
||||
```
|
||||
Then you can backup:
|
||||
```bash
|
||||
mongodump --port 27019
|
||||
```
|
||||
And restore:
|
||||
```bash
|
||||
mongorestore --drop --port 27019
|
||||
```
|
||||
|
||||
## MongoDB shell on Wekan Snap
|
||||
|
||||
mongoshell.sh
|
||||
```bash
|
||||
#/bin/bash
|
||||
export LC_ALL=C
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
|
||||
version=$(snap list | grep wekan | awk -F ' ' '{print $3}')
|
||||
mongo=$"/snap/wekan/$version/bin/mongo"
|
||||
$mongo --port 27019
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
# Snap backup-restore v2
|
||||
|
||||
Originally from https://github.com/wekan/wekan-snap/issues/62#issuecomment-470622601
|
||||
|
||||
## Backup
|
||||
|
||||
wekan-backup.sh
|
||||
```bash
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
|
||||
|
||||
version=$(snap list | grep wekan | awk -F ' ' '{print $3}')
|
||||
now=$(date +"%Y%m%d-%H%M%S")
|
||||
parent_dir="/data/backups/wekan"
|
||||
backup_dir="${parent_dir}/${now}"
|
||||
log_file="${parent_dir}/backup-progress.log.${now}"
|
||||
|
||||
error () {
|
||||
printf "%s: %s\n" "$(basename "${BASH_SOURCE}")" "${1}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
trap 'error "An unexpected error occurred."' ERR
|
||||
|
||||
take_backup () {
|
||||
mkdir -p "${backup_dir}"
|
||||
|
||||
cd "${backup_dir}"
|
||||
|
||||
/snap/wekan/$version/bin/mongodump --quiet --port 27019
|
||||
|
||||
cd ..
|
||||
|
||||
tar -zcf "${now}.tar.gz" "${now}"
|
||||
|
||||
rm -rf "${now}"
|
||||
}
|
||||
|
||||
printf "\n======================================================================="
|
||||
printf "\nWekan Backup"
|
||||
printf "\n======================================================================="
|
||||
printf "\nBackup in progress..."
|
||||
|
||||
take_backup 2> "${log_file}"
|
||||
|
||||
if [[ -s "${log_file}" ]]
|
||||
then
|
||||
printf "\nBackup failure! Check ${log_file} for more information."
|
||||
printf "\n=======================================================================\n\n"
|
||||
else
|
||||
rm "${log_file}"
|
||||
printf "...SUCCESS!\n"
|
||||
printf "Backup created at ${backup_dir}.tar.gz"
|
||||
printf "\n=======================================================================\n\n"
|
||||
fi
|
||||
```
|
||||
wekan-restore.sh
|
||||
```bash
|
||||
#!/bin/bash
|
||||
export LC_ALL=C
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
|
||||
|
||||
makesRestore()
|
||||
{
|
||||
file=$1
|
||||
|
||||
ext=$"$(basename $file)"
|
||||
parentDir=$"${file:0:${#file}-${#ext}}"
|
||||
cd "${parentDir}"
|
||||
|
||||
printf "\nMakes the untar of the archive.\n"
|
||||
|
||||
tar -zxvf "${file}"
|
||||
file="${file:0:${#file}-7}"
|
||||
|
||||
version=$(snap list | grep wekan | awk -F ' ' '{print $3}')
|
||||
|
||||
restore=$"/snap/wekan/${version}/bin/mongorestore"
|
||||
|
||||
printf "\nThe database restore is in progress.\n\n"
|
||||
|
||||
## Only if you get errors about existing indexes, use this below instead:
|
||||
## $restore --quiet --drop --noIndexRestore -d wekan --port 27019 "${file}/dump/wekan"
|
||||
|
||||
$restore --quiet --drop -d wekan --port 27019 "${file}/dump/wekan"
|
||||
|
||||
rm -rf "${file}"
|
||||
|
||||
printf "\nRestore done.\n"
|
||||
}
|
||||
|
||||
makesRestore $1
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
# Snap backup-restore v1
|
||||
|
||||
## Backup script for MongoDB Data, if running Snap MongoDB at port 27019
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
makeDump()
|
||||
{
|
||||
# Gets the version of the snap.
|
||||
version=$(snap list | grep wekan | awk -F ' ' '{print $3}')
|
||||
|
||||
# Prepares.
|
||||
now=$(date +'%Y-%m-%d_%H.%M.%S')
|
||||
mkdir -p /var/backups/wekan/$version-$now
|
||||
|
||||
# Targets the dump file.
|
||||
dump=$"/snap/wekan/$version/bin/mongodump"
|
||||
|
||||
# Makes the backup.
|
||||
cd /var/backups/wekan/$version-$now
|
||||
printf "\nThe database backup is in progress.\n\n"
|
||||
$dump --port 27019
|
||||
|
||||
# Makes the tar.gz file.
|
||||
cd ..
|
||||
printf "\nMakes the tar.gz file.\n"
|
||||
tar -zcvf $version-$now.tar.gz $version-$now
|
||||
|
||||
# Cleanups
|
||||
rm -rf $version-$now
|
||||
|
||||
# End.
|
||||
printf "\nBackup done.\n"
|
||||
echo "Backup is archived to .tar.gz file at /var/backups/wekan/${version}-${now}.tar.gz"
|
||||
}
|
||||
|
||||
# Checks is the user is sudo/root
|
||||
if [ "$UID" -ne "0" ]
|
||||
then
|
||||
echo "This program must be launched with sudo/root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Starts
|
||||
makeDump
|
||||
|
||||
```
|
||||
|
||||
## Restore script for MongoDB Data, if running Snap MongoDB at port 27019 with a tar.gz archive.
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
makesRestore()
|
||||
{
|
||||
# Prepares the folder used for the backup.
|
||||
file=$1
|
||||
if [[ "$file" != *tar.gz* ]]
|
||||
then
|
||||
echo "The backup archive must be a tar.gz."
|
||||
exit -1
|
||||
fi
|
||||
|
||||
# Goes into the parent directory.
|
||||
ext=$"$(basename $file)"
|
||||
parentDir=$"${file:0:${#file}-${#ext}}"
|
||||
cd $parentDir
|
||||
|
||||
# Untar the archive.
|
||||
printf "\nMakes the untar of the archive.\n"
|
||||
tar -zxvf $file
|
||||
file="${file:0:${#file}-7}"
|
||||
|
||||
# Gets the version of the snap.
|
||||
version=$(snap list | grep wekan | awk -F ' ' '{print $3}')
|
||||
|
||||
# Targets the dump file.
|
||||
restore=$"/snap/wekan/$version/bin/mongorestore"
|
||||
|
||||
# Restores.
|
||||
printf "\nThe database restore is in progress.\n\n"
|
||||
## Only if you get errors about existing indexes, use this below instead:
|
||||
## $restore --drop --noIndexRestore wekan --port 27019 $file/dump/wekan
|
||||
$restore --drop wekan --port 27019 $file/dump/wekan
|
||||
printf "\nRestore done.\n"
|
||||
|
||||
# Cleanups
|
||||
rm -rf $file
|
||||
}
|
||||
|
||||
# Checks is the user is sudo/root.
|
||||
if [ "$UID" -ne "0" ]
|
||||
then
|
||||
echo "This program must be launched with sudo/root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Start.
|
||||
makesRestore $1
|
||||
|
||||
```
|
||||
|
||||
## Docker Backup and Restore
|
||||
|
||||
[Docker Backup and Restore](Export-Docker-Mongo-Data)
|
||||
|
||||
[Wekan Docker Upgrade](https://github.com/wekan/wekan-mongodb#backup-before-upgrading)
|
||||
|
||||
## Snap Backup
|
||||
|
||||
[Snap Backup and Restore](https://github.com/wekan/wekan-snap/wiki/Backup-and-restore)
|
||||
|
||||
[Wekan Snap upgrade](https://github.com/wekan/wekan-snap/wiki/Install#5-install-all-snap-updates-automatically-between-0200am-and-0400am)
|
||||
|
||||
## Sandstorm Backup
|
||||
|
||||
Download Wekan grain with arrow down download button to .zip file. You can restore it later.
|
||||
|
||||
[Export data from Wekan Sandstorm grain .zip file](Export-from-Wekan-Sandstorm-grain-.zip-file)
|
||||
|
||||
## <a name="cloudron">Cloudron
|
||||
|
||||
If those [Backup](Backup) ways are not easily found at [Cloudron](Cloudron), one way is to install [Redash](https://redash.io/) and then backup this way:
|
||||
|
||||
Redash works with this kind of queries:
|
||||
```json
|
||||
{
|
||||
"collection": "accountSettings",
|
||||
"query": {
|
||||
"type": 1
|
||||
},
|
||||
"fields": {
|
||||
"_id": 1,
|
||||
"booleanValue": 1,
|
||||
"createdAt": 1,
|
||||
"modifiedAt": 1,
|
||||
}
|
||||
}
|
||||
```
|
||||
So:
|
||||
|
||||
1) Create this kind of query:
|
||||
```json
|
||||
{
|
||||
"collection": "boards"
|
||||
}
|
||||
```
|
||||
|
||||
Later when you modify query, you can remove text like boards with double-click-with-mouse and delete-key-at-keyboard (or select characters with mouse and delete-key-at-keyboard), and then click collection/table >> button to insert name of next collection/table.
|
||||
|
||||
2) Click Save
|
||||
|
||||
3) Click Execute. This will cache query for use with REST API.
|
||||
|
||||
4) Click at right top `[...]` => `Show API key`
|
||||
|
||||
It looks like this:
|
||||
|
||||
https://redash.example.com/api/queries/1/results.json?api_key=...
|
||||
|
||||
5) Only when saving first collection/table, Save API key to text file script like this `dl.sh`
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Example: ./dl.sh boards
|
||||
|
||||
export APIKEY=https://redash.example.com/api/queries/1/results.json?api_key=...
|
||||
|
||||
curl -o $1.json $APIKEY
|
||||
```
|
||||
|
||||
6) Run save script like:
|
||||
```bash
|
||||
./dl.sh boards
|
||||
```
|
||||
Note: 1) Save 2) Execute => webbrowser can give this kind of timeout,
|
||||
but downloading with API script still works:
|
||||
|
||||
> wekan
|
||||
> Error running query: failed communicating with server. Please check your Internet connection and try again.
|
||||
|
||||
7) Repeat steps 1-4 and 6 for every collection/table like boards,cards, etc
|
||||
|
||||
8) Remove from downloaded .json files extra query related data, so that it is similar like [any other Wekan database backup JSON files](Export-from-Wekan-Sandstorm-grain-.zip-file)
|
||||
|
||||
9) Insert data to some other Wekan install with nosqlbooster like mentioned at page [Backup](Backup)
|
||||
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
# Upgrading
|
||||
|
||||
Newest WeKan uses MongoDB 6.0.3.
|
||||
|
||||
Current working way to upgrade is:
|
||||
|
||||
## Docker
|
||||
|
||||
1. Create Mongodump of your database like at [Backup page](https://github.com/wekan/wekan/wiki/Backup)
|
||||
2. Use newest WeKan docker-compose.yml from https://github.com/wekan/wekan with WRITABLE_PATH setup to Rclone/MinIO like below this page
|
||||
3. Mongorestore to MongoDB 6.0.3 database
|
||||
4. Use WeKan Admin Panel `Move all attachments to filesystem` button to move all attachments to MinIO.
|
||||
5. Create Mongodump, that this time does not have any attachments, and is much smaller.
|
||||
6. Mongorestore to MongoDB 6.0.3 database.
|
||||
7. Start WeKan.
|
||||
|
||||
## Snap
|
||||
|
||||
Similar Mongodump and Mongorestore above, with these info:
|
||||
- https://github.com/wekan/wekan-snap/wiki/Candidate-WeKan-Snap-Manual-Upgrade
|
||||
- https://github.com/wekan/wekan/issues/4780
|
||||
|
||||
# Introduction
|
||||
|
||||
With Rclone https://rclone.org , it's possible to use many cloud filesystems, like AWS S3, MinIO https://min.io , etc.
|
||||
|
||||
Newest WeKan has features to move files between MongoDB GridFS and filesystem.
|
||||
|
||||
Instead of filesystem, Rclone mounted cloud filesystem directory can be used, like MinIO.
|
||||
|
||||
## Screenshot 1: Move to filesystem button moves to cloud filesystem like MinIO. S3 button does not work yet.
|
||||
|
||||
Note: In some cases, only buttons `Move all attachments` at top are visible. In some other cases, there is more visible, like moving all attachments of board, etc, maybe when some have been already moved.
|
||||
|
||||
<img src="https://wekan.github.io/rclone/wekan-admin-panel.png" width="100%" alt="Wekan Admin Panel file move" />
|
||||
|
||||
## Screenshot 2: Files at MinIO after moving all to filesystem
|
||||
|
||||
<img src="https://wekan.github.io/rclone/minio1.png" width="100%" alt="MinIO 1" />
|
||||
|
||||
## Screenshot 3: Files at MinIO after moving all to filesystem
|
||||
|
||||
<img src="https://wekan.github.io/rclone/minio2.png" width="100%" alt="MinIO 2" />
|
||||
|
||||
## Rclone config
|
||||
|
||||
Create config for example with command:
|
||||
```
|
||||
rclone config
|
||||
```
|
||||
At S3 providers, there is MinIO at https://rclone.org/s3/#minio
|
||||
|
||||
`/root/.config/rclone/rclone.conf`
|
||||
|
||||
```
|
||||
[aws]
|
||||
type = s3
|
||||
provider = AWS
|
||||
access_key_id = ACCESS-KEY-HERE
|
||||
secret_access_key = SECRET-KEY-HERE
|
||||
region = eu-north-1
|
||||
location_constraint = eu-north-1
|
||||
acl = private
|
||||
|
||||
[minio]
|
||||
type = s3
|
||||
provider = Minio
|
||||
access_key_id = ACCESS-KEY-HERE
|
||||
secret_access_key = SECRET-KEY-HERE
|
||||
endpoint = http://192.168.0.100:9000
|
||||
acl = private
|
||||
```
|
||||
## Listing files with Rclone
|
||||
```
|
||||
rclone ls aws:
|
||||
|
||||
rclone ls minio:
|
||||
```
|
||||
## MinIO config
|
||||
|
||||
https://min.io
|
||||
|
||||
Running MinIO server binary, storing files at `/home/wekan/minio/data`
|
||||
```
|
||||
MINIO_ROOT_USER=admin MINIO_ROOT_PASSWORD=password ./minio server /home/wekan/minio/data --console-address ":9001"
|
||||
```
|
||||
Create bucket, access key, secret key. Then connect them with `mc` command installed from https://min.io website, here bucket wekan:
|
||||
```
|
||||
mc config host add wekan http://192.168.0.100:9000 ACCESS-KEY-HERE SECRET-KEY-HERE
|
||||
```
|
||||
## Snap
|
||||
This uses minimal cache to make it work. Note that Rclone stays at foreground running, showing related messages, it does not daemonize to background.
|
||||
```
|
||||
sudo su
|
||||
|
||||
rclone mount minio:wekan/files /var/snap/wekan/common/files --vfs-cache-mode minimal
|
||||
```
|
||||
Sometimes it shows this kind of message:
|
||||
```
|
||||
2022/12/27 10:22:13 NOTICE: S3 bucket wekan path files: Streaming uploads using chunk size 5Mi will have maximum file size of 48.828Gi
|
||||
```
|
||||
## Docker
|
||||
|
||||
Rclone, short story:
|
||||
```
|
||||
sudo su
|
||||
|
||||
rclone mount minio:wekan/files /var/lib/docker/volumes/wekan_wekan-files/_data --vfs-cache-mode minimal
|
||||
```
|
||||
Longer story:
|
||||
|
||||
`docker-compose.yml` at https://github.com/wekan/wekan
|
||||
```
|
||||
- WRITABLE_PATH=/data
|
||||
```
|
||||
Docker volumes are there:
|
||||
```
|
||||
sudo ls /var/lib/docker/volumes/
|
||||
|
||||
wekan_wekan-db-dump
|
||||
wekan_wekan-db
|
||||
wekan_wekan-files
|
||||
```
|
||||
Directories there are:
|
||||
```
|
||||
/var/lib/docker/volumes/_data/attachments
|
||||
/var/lib/docker/volumes/_data/avatars
|
||||
```
|
||||
## Bundle or Source
|
||||
|
||||
Set `WRITABLE_PATH`like in Docker example above.
|
||||
|
||||
## Development
|
||||
|
||||
In Progress direct support without Rclone, if getting it working sometime.
|
||||
|
||||
Meteor-Files currently uses official AWS-SDK that does not yet directly support custom endpoint like MinIO:
|
||||
- https://github.com/veliovgroup/Meteor-Files/issues/862
|
||||
- https://github.com/veliovgroup/Meteor-Files/blob/master/docs/aws-s3-integration.md
|
||||
- https://github.com/wekan/wekan/issues/142#issuecomment-1365249290
|
||||
|
||||
MinIO code examples:
|
||||
- https://github.com/minio/minio-js/tree/master/examples
|
||||
|
||||
Related RocketChat docs about MinIO:
|
||||
- https://docs.rocket.chat/guides/administration/admin-panel/settings/file-upload/minio
|
||||
- https://github.com/RocketChat/Rocket.Chat/tree/develop/apps/meteor/app/file-upload
|
||||
- https://github.com/RocketChat/Rocket.Chat/pulls?q=is%3Apr+minio+is%3Aclosed
|
||||
|
||||
Storage path during developing WeKan, for example:
|
||||
```
|
||||
./rebuild-wekan.sh
|
||||
|
||||
rclone mount minio:wekan/files /home/wekan/repos/wekan/.meteor/local/.build-garbage-uslyxi.uokel/programs --vfs-cache-mode minimal
|
||||
```
|
||||
@@ -0,0 +1,165 @@
|
||||
## Repair Snap WeKan
|
||||
|
||||
```
|
||||
sudo su
|
||||
|
||||
snap stop wekan
|
||||
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu
|
||||
|
||||
export PATH="/snap/wekan/current/bin:$PATH"
|
||||
|
||||
mongod --dbpath "/var/snap/wekan/common" --repair >> /root/repairlog.txt
|
||||
|
||||
snap start wekan
|
||||
|
||||
exit
|
||||
```
|
||||
|
||||
Logs from Snap:
|
||||
|
||||
https://github.com/wekan/wekan/issues/5073
|
||||
|
||||
## Repair Docker WeKan
|
||||
|
||||
At step 12 below is actual repair MongoDB command.
|
||||
|
||||
Some customer of [WeKan Commercial Support](https://wekan.team/commercial-support/) ordered restore and repair of WeKan MongoDB database at UCS appliance. This was needed when changing back to previous UCS 4.x major version, when in new major UCS 5.x version Docker containers were all the time crashing and restarting, or something else got broken. Here are commands that were used while repairing.
|
||||
|
||||
Similarly, MongoDB may require repair, if:
|
||||
- MongoDB does not recover from sudden power failure cleanly
|
||||
- Disk comes full, that is worst case. Recommended are daily backups, in this case sometimes repair does not work well enough.
|
||||
|
||||
1. SSH to UCS appliance as root:
|
||||
```
|
||||
ssh root@192.168.0.100
|
||||
```
|
||||
2. If backup or raw database files is at external USB harddrive, look where it is inserted:
|
||||
```
|
||||
ls /dev
|
||||
|
||||
fdisk -l /dev/sdb
|
||||
|
||||
fdisk -l /dev/sdc
|
||||
|
||||
fdisk -l /dev/sdd
|
||||
```
|
||||
2. If external USB drive has NTFS file format, and mount complains about not having NTFS support, install NTFS support to Linux:
|
||||
```
|
||||
apt install ntfs-3g
|
||||
```
|
||||
3. Mount USB drive to /mnt
|
||||
```
|
||||
mount /dev/sdd1 /mnt
|
||||
```
|
||||
4. Stop WeKan Docker containers. If copying raw database files, WeKan and MongoDB should not be running, so that MongoDB would not be even more corrupted. (If MongoDB is running, and you have mongodump backup, you can [Backup with mongorestore](Backup)
|
||||
```
|
||||
docker stop wekan-db wekan-app
|
||||
```
|
||||
5. Find WeKan database raw files. MongoDB has various database engines, like WiredTiger, how to save raw data compressed, or other formats. WiredTiger is file format to save compressed MongoDB data. Similar like MySQL has ISAM, InnoDB etc.
|
||||
```
|
||||
apt -y install mlocate
|
||||
updatedb
|
||||
locate WiredTiger.wt
|
||||
```
|
||||
It can show for example USB drive and Docker container directory:
|
||||
```
|
||||
root@ucs:/mnt/wekan/data/db# locate WiredTiger.wt
|
||||
/mnt/wekan/data/db/WiredTiger.wt
|
||||
/var/lib/univention-appcenter/apps/wekan/data/db/WiredTiger.wt
|
||||
```
|
||||
6. Change to newly installed MongoDB data directory:
|
||||
```
|
||||
cd /var/lib/univention-appcenter/apps/wekan/data/db/
|
||||
```
|
||||
7. Look at directory files owner permissions:
|
||||
```
|
||||
ls -lah
|
||||
```
|
||||
There could be like this:
|
||||
```
|
||||
-rw-r--r-- 1 tss tss 1004 Jan 25 13:09 WiredTiger.turtle
|
||||
-rw-r--r-- 1 tss tss 392K Jan 25 13:09 WiredTiger.wt
|
||||
```
|
||||
8. Move that probably empty newly installed MongoDB data elsewhere:
|
||||
```
|
||||
mkdir /root/new-empty-wekan-data
|
||||
mv * /root/new-empty-wekan-data
|
||||
```
|
||||
9. Copy MongoDB raw database files from USB harddrive to docker container directory:
|
||||
```
|
||||
root@ucs-bdc:/var/lib/univention-appcenter/apps/wekan/data/db# cp -pR /mnt/wekan/data/db/* .
|
||||
```
|
||||
10. Like looked at step 7, change file owner permissions to be correct, and change directory
|
||||
```
|
||||
chown -R tss:tss *
|
||||
cd /root
|
||||
```
|
||||
11. Find mongod command:
|
||||
```
|
||||
locate /usr/bin/mongod
|
||||
```
|
||||
12. Repair Docker WeKan with version of Docker MongoDB that WeKan uses, change mongod path to below. Repairing logged to textfile.
|
||||
```
|
||||
root@ucs:~# /var/lib/docker/overlay2/7b58483a16a2f67ee50486c00ec669940f7a95d460ee8188966fee0096e81fa2/diff/usr/bin/mongod --dbpath "/var/lib/univention-appcenter/apps/wekan/data/db" --repair >> repairlog.txt
|
||||
```
|
||||
13. Look what containers are running:
|
||||
```
|
||||
docker ps
|
||||
```
|
||||
14. Look what containers are not running:
|
||||
```
|
||||
docker ps -a
|
||||
```
|
||||
15. Start MongoDB first:
|
||||
```
|
||||
docker start wekan-db
|
||||
```
|
||||
16. Look what happends at MongoDB container:
|
||||
```
|
||||
docker logs wekan-db
|
||||
```
|
||||
17. Start WeKan next.
|
||||
```
|
||||
docker start wekan-app
|
||||
```
|
||||
18. Look what happends at MongoDB container.
|
||||
```
|
||||
docker logs wekan-app
|
||||
```
|
||||
19. Check are both wekan-db and wekan-app containers running, and not restarting:
|
||||
```
|
||||
docker ps
|
||||
```
|
||||
20. Try to login to WeKan with webbrowser.
|
||||
21. Backup WeKan database now after repair:
|
||||
```
|
||||
docker exec -it wekan-db bash
|
||||
cd /data
|
||||
rm -rf dump
|
||||
mongodump
|
||||
exit
|
||||
docker cp wekan-db:/data/dump .
|
||||
zip -r wekan-backup-YEAR-MONTH-DATE-TIME-HERE.zip dump
|
||||
```
|
||||
Now backup is at wekan-backup-YEAR-MONTH-DATE-TIME-HERE.zip file.
|
||||
|
||||
22. At your local computer terminal (not at UCS server), transfer file to your local computer with scp:
|
||||
```
|
||||
scp root@192.168.0.100:/root/wekan-backup-YEAR-MONTH-DATE-TIME-HERE.zip .
|
||||
```
|
||||
23. Copy backup to external USB harddrive, change YEAR-MONTH-DATE-TIME-HERE to current date and time:
|
||||
```
|
||||
cp /root/wekan-backup-YEAR-MONTH-DATE-TIME-HERE.zip /mnt/
|
||||
```
|
||||
24. Look what drives are mounted:
|
||||
```
|
||||
df -h
|
||||
```
|
||||
25. Sync and save unwritted data to disk, and unmount external USB harddrive safely:
|
||||
```
|
||||
sync
|
||||
umount /dev/sdd1
|
||||
```
|
||||
26. Remove external USB harddisk from server.
|
||||
27. Login to WeKan, check do all WeKan boards work. In this case, all did work.
|
||||
Loaded 100 of 649 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user