diff options
77 files changed, 4049 insertions, 301 deletions
diff --git a/.meteor/.finished-upgraders b/.meteor/.finished-upgraders index 2a56593d..8f397c7d 100644 --- a/.meteor/.finished-upgraders +++ b/.meteor/.finished-upgraders @@ -16,3 +16,4 @@ notices-for-facebook-graph-api-2 1.4.1-add-shell-server-package 1.4.3-split-account-service-packages 1.5-add-dynamic-import-package +1.7-split-underscore-from-meteor-base diff --git a/.meteor/packages b/.meteor/packages index 0bb90513..65d54fd2 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -3,17 +3,18 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. -meteor-base@1.2.0 +meteor-base@1.4.0 # Build system -ecmascript +ecmascript@0.12.0 stylus@2.513.13 -standard-minifier-css@1.3.5 -standard-minifier-js@2.2.0 +standard-minifier-css@1.5.0 +standard-minifier-js@2.4.0 mquandalle:jade +coffeescript@2.3.1_2! # Polyfills -es5-shim@4.6.15 +es5-shim@4.8.0 # Collections aldeed:collection2 @@ -23,7 +24,7 @@ dburles:collection-helpers idmontie:migrations matb33:collection-hooks matteodem:easy-search -mongo@1.3.1 +mongo@1.6.0 mquandalle:collection-mutations # Account system @@ -31,14 +32,15 @@ kenton:accounts-sandstorm service-configuration@1.0.11 useraccounts:unstyled useraccounts:flow-routing +salleman:accounts-oidc # Utilities -check@1.2.5 +check@1.3.1 jquery@1.11.10 -random@1.0.10 -reactive-dict@1.2.0 -session@1.1.7 -tracker@1.1.3 +random@1.1.0 +reactive-dict@1.2.1 +session@1.1.8 +tracker@1.2.0 underscore@1.0.10 3stack:presence alethes:pages @@ -52,7 +54,7 @@ mquandalle:autofocus ongoworks:speakingurl raix:handlebar-helpers tap:i18n -http@1.3.0 +http@1.4.1 # UI components blaze @@ -69,20 +71,22 @@ templates:tabs verron:autosize simple:json-routes rajit:bootstrap3-datepicker -shell-server@0.3.0 +shell-server@0.4.0 simple:rest-accounts-password useraccounts:core email@1.2.3 horka:swipebox -dynamic-import@0.2.0 +dynamic-import@0.5.0 staringatlights:fast-render mixmax:smart-disconnect -accounts-password@1.5.0 +accounts-password@1.5.1 cfs:gridfs eluck:accounts-lockout rzymek:fullcalendar momentjs:moment@2.22.2 -browser-policy-framing +browser-policy-framing@1.1.0 mquandalle:moment msavin:usercache +wekan:wekan-ldap +wekan:accounts-cas diff --git a/.meteor/release b/.meteor/release index 56a7a07f..02806a3f 100644 --- a/.meteor/release +++ b/.meteor/release @@ -1 +1 @@ -METEOR@1.6.0.1 +METEOR@1.8.1-beta.0 diff --git a/.meteor/versions b/.meteor/versions index fc7d64b0..8d10ad73 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -1,6 +1,7 @@ 3stack:presence@1.1.2 -accounts-base@1.4.0 -accounts-password@1.5.0 +accounts-base@1.4.3 +accounts-oauth@1.1.16 +accounts-password@1.5.1 aldeed:collection2@2.10.0 aldeed:collection2-core@1.2.0 aldeed:schema-deny@1.1.0 @@ -10,19 +11,19 @@ alethes:pages@1.8.6 allow-deny@1.1.0 arillo:flow-router-helpers@0.5.2 audit-argument-checks@1.0.7 -autoupdate@1.3.12 -babel-compiler@6.24.7 -babel-runtime@1.1.1 -base64@1.0.10 -binary-heap@1.0.10 -blaze@2.3.2 +autoupdate@1.5.0 +babel-compiler@7.2.0 +babel-runtime@1.3.0 +base64@1.0.11 +binary-heap@1.0.11 +blaze@2.3.3 blaze-tools@1.0.10 -boilerplate-generator@1.3.1 +boilerplate-generator@1.6.0 browser-policy-common@1.0.11 browser-policy-framing@1.1.0 -caching-compiler@1.1.9 +caching-compiler@1.2.0 caching-html-compiler@1.1.2 -callback-hook@1.0.10 +callback-hook@1.1.0 cfs:access-point@0.1.49 cfs:base-package@0.0.30 cfs:collection@0.5.5 @@ -40,38 +41,40 @@ cfs:storage-adapter@0.2.3 cfs:tempstore@0.1.5 cfs:upload-http@0.0.20 cfs:worker@0.1.4 -check@1.2.5 +check@1.3.1 chuangbo:cookie@1.1.0 -coffeescript@1.12.7_3 -coffeescript-compiler@1.12.7_3 +coffeescript@2.3.1_2 +coffeescript-compiler@2.3.1_2 cottz:publish-relations@2.0.8 dburles:collection-helpers@1.1.0 ddp@1.4.0 -ddp-client@2.2.0 -ddp-common@1.3.0 +ddp-client@2.3.3 +ddp-common@1.4.0 ddp-rate-limiter@1.0.7 -ddp-server@2.1.1 +ddp-server@2.2.0 deps@1.0.12 -diff-sequence@1.0.7 -dynamic-import@0.2.1 -ecmascript@0.9.0 -ecmascript-runtime@0.5.0 -ecmascript-runtime-client@0.5.0 -ecmascript-runtime-server@0.5.0 +diff-sequence@1.1.0 +dynamic-import@0.5.0 +ecmascript@0.12.0 +ecmascript-runtime@0.7.0 +ecmascript-runtime-client@0.8.0 +ecmascript-runtime-server@0.7.1 ejson@1.1.0 eluck:accounts-lockout@0.9.0 email@1.2.3 -es5-shim@4.6.15 +es5-shim@4.8.0 fastclick@1.0.13 +fetch@0.1.0 fortawesome:fontawesome@4.7.0 geojson-utils@1.0.10 horka:swipebox@1.0.2 hot-code-push@1.0.4 html-tools@1.0.11 htmljs@1.0.11 -http@1.3.0 -id-map@1.0.9 +http@1.4.1 +id-map@1.1.0 idmontie:migrations@1.0.3 +inter-process-messaging@0.1.0 jquery@1.11.10 kadira:blaze-layout@2.3.0 kadira:dochead@1.5.0 @@ -80,12 +83,12 @@ kenton:accounts-sandstorm@0.7.0 launch-screen@1.1.1 livedata@1.0.18 localstorage@1.2.0 -logging@1.1.19 +logging@1.1.20 matb33:collection-hooks@0.8.4 matteodem:easy-search@1.6.4 mdg:validation-error@0.5.1 -meteor@1.8.2 -meteor-base@1.2.0 +meteor@1.9.2 +meteor-base@1.4.0 meteor-platform@1.2.6 meteorhacks:aggregate@1.3.0 meteorhacks:collection-utils@1.2.0 @@ -93,18 +96,20 @@ meteorhacks:meteorx@1.4.1 meteorhacks:picker@1.0.3 meteorhacks:subs-manager@1.6.4 meteorspark:util@0.2.0 -minifier-css@1.2.16 -minifier-js@2.2.2 +minifier-css@1.4.0 +minifier-js@2.4.0 minifiers@1.1.8-faster-rebuild.0 -minimongo@1.4.3 +minimongo@1.4.5 mixmax:smart-disconnect@0.0.4 mobile-status-bar@1.0.14 -modules@0.11.0 -modules-runtime@0.9.1 +modern-browsers@0.1.2 +modules@0.13.0 +modules-runtime@0.10.2 momentjs:moment@2.22.2 -mongo@1.3.1 +mongo@1.6.0 +mongo-decimal@0.1.0 mongo-dev-server@1.1.0 -mongo-id@1.0.6 +mongo-id@1.0.7 mongo-livedata@1.0.12 mousetrap:mousetrap@1.4.6_1 mquandalle:autofocus@1.0.0 @@ -118,43 +123,48 @@ mquandalle:mousetrap-bindglobal@0.0.1 mquandalle:perfect-scrollbar@0.6.5_2 msavin:usercache@1.0.0 npm-bcrypt@0.9.3 -npm-mongo@2.2.33 +npm-mongo@3.1.1 +oauth@1.2.3 +oauth2@1.2.1 observe-sequence@1.0.16 ongoworks:speakingurl@1.1.0 -ordered-dict@1.0.9 +ordered-dict@1.1.0 peerlibrary:assert@0.2.5 peerlibrary:base-component@0.16.0 peerlibrary:blaze-components@0.15.1 -peerlibrary:computed-field@0.7.0 +peerlibrary:computed-field@0.9.0 peerlibrary:reactive-field@0.3.0 perak:markdown@1.0.5 -promise@0.10.0 +promise@0.11.1 raix:eventemitter@0.1.3 raix:handlebar-helpers@0.2.5 rajit:bootstrap3-datepicker@1.7.1 -random@1.0.10 -rate-limit@1.0.8 -reactive-dict@1.2.0 +random@1.1.0 +rate-limit@1.0.9 +reactive-dict@1.2.1 reactive-var@1.0.11 -reload@1.1.11 -retry@1.0.9 -routepolicy@1.0.12 +reload@1.2.0 +retry@1.1.0 +routepolicy@1.1.0 rzymek:fullcalendar@3.8.0 +salleman:accounts-oidc@1.0.9 +salleman:oidc@1.0.9 service-configuration@1.0.11 -session@1.1.7 +session@1.1.8 sha@1.0.9 -shell-server@0.3.1 +shell-server@0.4.0 simple:authenticate-user-by-token@1.0.1 simple:json-routes@2.1.0 simple:rest-accounts-password@1.1.2 simple:rest-bearer-token-parser@1.0.1 simple:rest-json-error-handler@1.0.1 +socket-stream-client@0.2.2 softwarerero:accounts-t9n@1.3.11 spacebars@1.0.15 spacebars-compiler@1.1.3 -srp@1.0.10 -standard-minifier-css@1.3.5 -standard-minifier-js@2.2.3 +srp@1.0.12 +standard-minifier-css@1.5.0 +standard-minifier-js@2.4.0 staringatlights:fast-render@2.16.5 staringatlights:inject-data@2.0.5 stylus@2.513.13 @@ -164,14 +174,17 @@ templating@1.3.2 templating-compiler@1.3.3 templating-runtime@1.3.2 templating-tools@1.1.2 -tracker@1.1.3 +tracker@1.2.0 ui@1.0.13 underscore@1.0.10 -url@1.1.0 +url@1.2.0 useraccounts:core@1.14.2 useraccounts:flow-routing@1.14.2 useraccounts:unstyled@1.14.2 verron:autosize@3.0.8 -webapp@1.4.0 +webapp@1.7.0 webapp-hashing@1.0.9 +wekan:accounts-cas@0.1.0 +wekan:wekan-ldap@0.0.2 +yasaricli:slugify@0.0.7 zimme:active-route@2.3.2 diff --git a/CHANGELOG.md b/CHANGELOG.md index d4740127..b4bab0ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# v1.55 2018-10-16 Wekan release +# v1.55.1 2018-10-16 Wekan Edge release This release adds the following new features: @@ -6,66 +6,178 @@ This release adds the following new features: and fixes the following bugs: -- [Update broke the ability to mute notifications](https://github.com/wekan/wekan/issues/1952). +- [LDAP: Include missing LDAP PR so that LDAP works](https://github.com/wekan/wekan-ldap/pull/6); +- [Improve notifications](https://github.com/wekan/wekan/pull/1948); +- [Fix deleting Custom Fields, removing broken references](https://github.com/wekan/wekan/issues/1872); +- [Fix vertical text for swimlanes in IE11](https://github.com/wekan/wekan/issues/1798); +- [Update broke the ability to mute notifications](https://github.com/wekan/wekan/pull/1954). -Thanks to GitHub user Akuket for contributions. +Thanks to GitHub users Akuket, Clement87 and tomodwyer for their contributions. -# v1.54 2018-10-14 Wekan release +# v1.53.9 2018-10-11 Wekan Edge release -This release fixes the following bugs: +This release adds the following new features: -- [Fix vertical text for swimlanes in IE11](https://github.com/wekan/wekan/issues/1798). -- [Fix deleting Custom Fields, removing broken references](https://github.com/wekan/wekan/issues/1872). -- [Improve notifications](https://github.com/wekan/wekan/pull/1948). -- [REST API: Get cards by swimlane id](https://github.com/wekan/wekan/pull/1944). Please [add docs](https://github.com/wekan/wekan/wiki/REST-API-Swimlanes). +- docker-compose.yml in this Edge branch now works with Wekan Edge + Meteor 1.8.1-beta.0 + MongoDB 4.0.3; +- [Snap is still broken](https://forum.snapcraft.io/t/how-to-connect-to-localhost-mongodb-in-snap-apparmor-prevents/7793/2). Please use latest Snap release on Edge branch, until this is fixed. -Thanks to GitHub users Akuket, Clement87, dcmcand and tomodwyer for their contributions. +Thanks to GitHub user xet7 for contributions. -# v1.53 2018-10-03 Wekan release +# v1.53.8 2018-10-10 Wekan Edge release -This release fixes the following bugs: +This release tries to fix the following bugs: + +- Try to fix Docker. + +Thanks to GitHub user xet7 for contributions. + +# v1.53.7 2018-10-10 Wekan Edge release + +This release adds the following new features: + +- Try MongoDB 4.0.3 + +Thanks to GitHub user xet7 for contributions. + +# v1.53.6 2018-10-10 Wekan Edge release + +This release adds the following new features: + +- [Add LDAP to Snap Help](https://github.com/wekan/wekan/commit/809c8f64f69721d51b7d963248a77585867fac53). + +and tries to fix the following bugs: + +- Try to fix snap. + +Thanks to GitHub users Akuket and xet7 for their contributions. + +# v1.53.5 2018-10-10 Wekan Edge relase + +This release tries to fix the following bugs: + +- Try to fix snap. + +Thanks to GitHub user xet7 for contributions. + +# v1.53.4 2018-10-10 Wekan Edge release + +This release adds the following new features: + +- [Upgrade Hoek](https://github.com/wekan/wekan/commit/0b971b6ddb1ffc4adad6b6b09ae7f42dd376fe2c). + +Thanks to GitHub user xet7 for contributions. + +# v1.53.3 2018-10-10 Wekan Edge release + +This release adds the following new features: + +- [Upgrade](https://github.com/wekan/wekan/issues/1522) to [Meteor](https://blog.meteor.com/meteor-1-8-erases-the-debts-of-1-7-77af4c931fe3) [1.8.1-beta.0](https://github.com/meteor/meteor/issues/10216). + with [these](https://github.com/wekan/wekan/commit/079e45eb52a0f62ddb6051bf2ea80fac8860d3d5) + [commits](https://github.com/wekan/wekan/commit/dd47d46f4341a8c4ced05749633f783e88623e1b). So now it's possible to use MongoDB 2.6 - 4.0. + +Thanks to GitHub user xet7 for contributions. +# v1.53.2 2018-10-10 Wekan Edge release + +This release adds the following new features: + +- [Add LDAP package to Docker and Snap](https://github.com/wekan/wekan/commit/f599391419bc7422a6ead52cdefc7d380e787897). + +Thanks to GitHub user xet7 for contributions. + +# v1.53.1 2018-10-10 Wekan Edge release + +This release adds the following new features: + +- [LDAP](https://github.com/wekan/wekan/commit/288800eafc91d07f859c4f59588e0b646137ccb9). + Please test and [add info about bugs](https://github.com/wekan/wekan/issues/119); +- [Add LDAP support and authentications dropdown menu on login page](https://github.com/wekan/wekan/pull/1943); +- [REST API: Get cards by swimlane id](https://github.com/wekan/wekan/pull/1944). Please [add docs](https://github.com/wekan/wekan/wiki/REST-API-Swimlanes). + +and fixes the following bugs: + +- [OpenShift: Drop default namespace value and duplicate WEKAN_SERVICE_NAME parameter.commit](https://github.com/wekan/wekan/commit/fcc3560df4dbcc418c63470776376238af4f6ddc); - [Fix Card URL](https://github.com/wekan/wekan/pull/1932); -- [OpenShift: Drop default namespace value and duplicate WEKAN_SERVICE_NAME parameter](https://github.com/wekan/wekan/pull/1930); -- [Add whole packages/ directory to .gitignore](https://github.com/wekan/wekan/commit/f437d8370e03439d7ba5649496ec188c5d7b7e0c); -- [Add info about root-url to GitHub issue template](https://github.com/wekan/wekan/commit/001c8f2b0138fb26a8c84acab62a604d0c6e5dda). +- [Add info about root-url to GitHub issue template](https://github.com/wekan/wekan/commit/4c0eb7dcc19ca9ae8c5d2d0276e0d024269de236); +- [Feature rules: fixes and enhancements](https://github.com/wekan/wekan/pull/1936). + +Thanks to GitHub users Akuket, Angtrim, dcmcand, lberk, maximest-pierre, InfoSec812, schulz and xet7 for their contributions. + +# v1.52.1 2018-10-02 Wekan Edge release -Thanks to GitHub users lberk, InfoSec812 and xet7 for their contributions. +This release adds the following new features: -# v1.52 2018-10-01 Wekan release +- REST API: [Add member with role to board. Remove member from board](https://github.com/wekan/wekan/commit/33caf1809a459b136b671f7061f08eb5e8d5e920). + [Docs](https://github.com/wekan/wekan/wiki/REST-API-Role). Related to [role issue](https://github.com/wekan/wekan/issues/1861). -This release removes the following new features: +and reverts previous change: -- [Removed CAS from Wekan stable](https://github.com/wekan/wekan/commit/5923585584f9cb8121476bf5e5d0abf7891e86f6), - because [it does not work correctly](https://github.com/wekan/wekan/issues/1925). - CAS developent continues at edge. +- OAuth2: [Revert Oidc preferred_username back to username](https://github.com/wekan/wekan/commit/33caf1809a459b136b671f7061f08eb5e8d5e920). + This [does not fix or break anything](https://github.com/wekan/wekan/issues/1874#issuecomment-425179291), + Oidc already works with [doorkeeper](https://github.com/doorkeeper-gem/doorkeeper-provider-app). Thanks to GitHub user xet7 for contributions. + +# v1.51.2 2018-09-30 Wekan Edge release + +This release adds the following new features: -# v1.51 2018-09-28 Wekan release +- [REST API: Change role of board member](https://github.com/wekan/wekan/commit/51ac6c839ecf2226b2a81b0d4f985d3b942f0938). + Docs: https://github.com/wekan/wekan/wiki/REST-API-Role + +Thanks to GitHub users entrptaher and xet7 for their contributions. + +# v1.51.1 2018-09-28 Wekan Edge release This release adds the following new features: -- [Add CAS with attributes](https://github.com/wekan/wekan/commit/c6cea2fb4e9e17403fe0ce2ba5bf2d20dcf81a8f); -- [Move Add Board button to top left, so there is no need to scroll to bottom when there is a lot of boards](https://github.com/wekan/wekan/commit/a10b6fb173d529220861668cfb1c341ec45e2a53). +- [Add CAS with attributes](https://github.com/wekan/wekan/commit/bd6e4a351b984b032e17c57793a70923eb17d8f5); +- [Move Add Board button to top left, so there is no need to scroll to bottom when there is a lot of boards](https://github.com/wekan/wekan/commit/fb46a88a0f01f7f74ae6b941dd6f2060e020f09d). Thanks to GitHub users ppoulard and xet7 for their contributions. -# v1.50 2018-09-22 Wekan release +# v1.50.3 2018-09-23 Wekan Edge release + +This release tries to fix the following bugs: + +- [Remove "Fix Cannot setup mail server via snap variables"](https://github.com/wekan/wekan/commit/6d88baebc7e297ffdbbd5bb6971190b18f79d21f) + to see does Wekan Snap start correctly after removing it. + +Thanks to GitHub user xet7 for contributions. + +# v1.50.2 2018-09-23 Wekan Edge release + +This release tries to fix the following bugs: + +- Build Wekan and release again, to see does it work. + +Thanks to GitHub user xet7 for contributions. + +# v1.50.1 2018-09-22 Wekan Edge release This release adds the following new features: -- [Change from Node v8.12.0 prerelease to use official Node v8.12.0](https://github.com/wekan/wekan/commit/bfabd6346033c3d3887a4693de8f13bc1705b582). +- [Change from Node v8.12.0 prerelease to use official Node v8.12.0](https://github.com/wekan/wekan/commit/7ec7a5f27c381e90f3da6bddc3773ed87b1c1a1f). and fixes the following bugs: -- [Fix Dockerfile Meteor install by changing tar to bsdtar](https://github.com/wekan/wekan/commit/352e9033b6efb212e65e34bb9c407bb1d7dce824); -- Add [npm-debug.log](https://github.com/wekan/wekan/commit/f7731f4f5ec27e63e74a3265d105427ef3c0985a) and - [.DS_Store](https://github.com/wekan/wekan/commit/d652eb5cee3fd648a6023e38db444ad460ddef7e) to .gitignore; -- [Add more debug log requirements to GitHub issue template](https://github.com/wekan/wekan/commit/94cd2ce69098f02e4ac4bebb1a2b5eaf919f1020); -- [Add default Wekan Snap MongoDB bind IP 127.0.0.1](https://github.com/wekan/wekan/commit/12656ee9a13d2464cdc183590c76d3e09486c607). +- [Fix Dockerfile Meteor install by changing tar to bsdtar](https://github.com/wekan/wekan/commit/1bad81ca86ca87c02148764cc03a3070882a8a33); +- Add [npm-debug.log and .DS_Store](https://github.com/wekan/wekan/commit/44f4a1c3bf8033b6b658703a0ccaed5fdb183ab4) to .gitignore; +- [Add more debug log requirements to GitHub issue template](https://github.com/wekan/wekan/commit/1c4ce56b0f18e00e01b54c7059cbbf8d3e196154); +- [Add default Wekan Snap MongoDB bind IP 127.0.0.1](https://github.com/wekan/wekan/commit/6ac726e198933ee41c129d22a7118fcfbf4ca9a2); +- [Fix Feature Rules](https://github.com/wekan/wekan/pull/1909); +- [Fix Cannot setup mail server via snap variables](https://github.com/wekan/wekan/issues/1906); +- [Try to fix OAuth2: Change oidc username to preferred_username](https://github.com/wekan/wekan/commit/734e4e5f3ff2c3dabf94c0fbfca561db066c4565). + +Thanks to GitHub users Angtrim, maurice-schleussinger, suprovsky and xet7 for their contributions. -Thanks to GitHub users maurice-schleussinger and xet7 for their contributions. +# v1.49.1 2018-09-17 Wekan Edge release + +This release adds the following new features: + +- Change from Node v8.12.0 prerelease to use official Node v8.12.0. + +Thanks to GitHub user xet7 for contributions. # v1.49 2018-09-17 Wekan release @@ -18,19 +18,59 @@ ARG MATOMO_WITH_USERNAME ARG BROWSER_POLICY_ENABLED ARG TRUSTED_URL ARG WEBHOOKS_ATTRIBUTES +ARG OAUTH2_ENABLED ARG OAUTH2_CLIENT_ID ARG OAUTH2_SECRET ARG OAUTH2_SERVER_URL ARG OAUTH2_AUTH_ENDPOINT ARG OAUTH2_USERINFO_ENDPOINT ARG OAUTH2_TOKEN_ENDPOINT +ARG LDAP_ENABLE +ARG LDAP_PORT +ARG LDAP_HOST +ARG LDAP_BASEDN +ARG LDAP_LOGIN_FALLBACK +ARG LDAP_RECONNECT +ARG LDAP_TIMEOUT +ARG LDAP_IDLE_TIMEOUT +ARG LDAP_CONNECT_TIMEOUT +ARG LDAP_AUTHENTIFICATION +ARG LDAP_AUTHENTIFICATION_USERDN +ARG LDAP_AUTHENTIFICATION_PASSWORD +ARG LDAP_LOG_ENABLED +ARG LDAP_BACKGROUND_SYNC +ARG LDAP_BACKGROUND_SYNC_INTERVAL +ARG LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED +ARG LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS +ARG LDAP_ENCRYPTION +ARG LDAP_CA_CERT +ARG LDAP_REJECT_UNAUTHORIZED +ARG LDAP_USER_SEARCH_FILTER +ARG LDAP_USER_SEARCH_SCOPE +ARG LDAP_USER_SEARCH_FIELD +ARG LDAP_SEARCH_PAGE_SIZE +ARG LDAP_SEARCH_SIZE_LIMIT +ARG LDAP_GROUP_FILTER_ENABLE +ARG LDAP_GROUP_FILTER_OBJECTCLASS +ARG LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE +ARG LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE +ARG LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT +ARG LDAP_GROUP_FILTER_GROUP_NAME +ARG LDAP_UNIQUE_IDENTIFIER_FIELD +ARG LDAP_UTF8_NAMES_SLUGIFY +ARG LDAP_USERNAME_FIELD +ARG LDAP_MERGE_EXISTING_USERS +ARG LDAP_SYNC_USER_DATA +ARG LDAP_SYNC_USER_DATA_FIELDMAP +ARG LDAP_SYNC_GROUP_ROLES +ARG LDAP_DEFAULT_DOMAIN # 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" ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential python git ca-certificates gcc-7" \ NODE_VERSION=v8.12.0 \ - METEOR_RELEASE=1.6.0.1 \ + METEOR_RELEASE=1.8.1-beta.0 \ USE_EDGE=false \ METEOR_EDGE=1.5-beta.17 \ NPM_VERSION=latest \ @@ -45,12 +85,52 @@ ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential pyth BROWSER_POLICY_ENABLED=true \ TRUSTED_URL="" \ WEBHOOKS_ATTRIBUTES="" \ + OAUTH2_ENABLED=false \ OAUTH2_CLIENT_ID="" \ OAUTH2_SECRET="" \ OAUTH2_SERVER_URL="" \ OAUTH2_AUTH_ENDPOINT="" \ OAUTH2_USERINFO_ENDPOINT="" \ - OAUTH2_TOKEN_ENDPOINT="" + OAUTH2_TOKEN_ENDPOINT="" \ + LDAP_ENABLE=false \ + LDAP_PORT=389 \ + LDAP_HOST="" \ + LDAP_BASEDN="" \ + LDAP_LOGIN_FALLBACK=false \ + LDAP_RECONNECT=true \ + LDAP_TIMEOUT=10000 \ + LDAP_IDLE_TIMEOUT=10000 \ + LDAP_CONNECT_TIMEOUT=10000 \ + LDAP_AUTHENTIFICATION=false \ + LDAP_AUTHENTIFICATION_USERDN="" \ + LDAP_AUTHENTIFICATION_PASSWORD="" \ + LDAP_LOG_ENABLED=false \ + LDAP_BACKGROUND_SYNC=false \ + LDAP_BACKGROUND_SYNC_INTERVAL=100 \ + LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false \ + LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false \ + LDAP_ENCRYPTION=false \ + LDAP_CA_CERT="" \ + LDAP_REJECT_UNAUTHORIZED=false \ + LDAP_USER_SEARCH_FILTER="" \ + LDAP_USER_SEARCH_SCOPE="" \ + LDAP_USER_SEARCH_FIELD="" \ + LDAP_SEARCH_PAGE_SIZE=0 \ + LDAP_SEARCH_SIZE_LIMIT=0 \ + LDAP_GROUP_FILTER_ENABLE=false \ + LDAP_GROUP_FILTER_OBJECTCLASS="" \ + LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE="" \ + LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE="" \ + LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT="" \ + LDAP_GROUP_FILTER_GROUP_NAME="" \ + LDAP_UNIQUE_IDENTIFIER_FIELD="" \ + LDAP_UTF8_NAMES_SLUGIFY=true \ + LDAP_USERNAME_FIELD="" \ + LDAP_MERGE_EXISTING_USERS=false \ + LDAP_SYNC_USER_DATA=false \ + LDAP_SYNC_USER_DATA_FIELDMAP="" \ + LDAP_SYNC_GROUP_ROLES="" \ + LDAP_DEFAULT_DOMAIN="" # Copy the app to the image COPY ${SRC_PATH} /home/wekan/app @@ -128,7 +208,8 @@ RUN \ # Change user to wekan and install meteor cd /home/wekan/ && \ chown wekan:wekan --recursive /home/wekan && \ - curl "https://install.meteor.com/?release=${METEOR_RELEASE}" -o /home/wekan/install_meteor.sh && \ + curl "https://install.meteor.com" -o /home/wekan/install_meteor.sh && \ + #curl "https://install.meteor.com/?release=${METEOR_RELEASE}" -o /home/wekan/install_meteor.sh && \ # OLD: sed -i "s|RELEASE=.*|RELEASE=${METEOR_RELEASE}\"\"|g" ./install_meteor.sh && \ # Install Meteor forcing its progress sed -i 's/VERBOSITY="--silent"/VERBOSITY="--progress-bar"/' ./install_meteor.sh && \ @@ -148,6 +229,8 @@ RUN \ cd /home/wekan/app/packages && \ gosu wekan:wekan git clone --depth 1 -b master git://github.com/wekan/flow-router.git kadira-flow-router && \ gosu wekan:wekan git clone --depth 1 -b master git://github.com/meteor-useraccounts/core.git meteor-useraccounts-core && \ + gosu wekan:wekan git clone --depth 1 -b master git://github.com/wekan/meteor-accounts-cas.git && \ + gosu wekan:wekan git clone --depth 1 -b master git://github.com/wekan/wekan-ldap.git && \ 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; \ @@ -4,6 +4,8 @@ - master+devel branch. At release, devel is merged to master. - Receives fixes and features that have been tested at edge that they work. +- If you want automatic updates, [use Snap](https://github.com/wekan/wekan-snap/wiki/Install). +- If you want to test before update, [use Docker quay.io release tags](https://github.com/wekan/wekan/wiki/Docker). ## Edge diff --git a/client/components/activities/activities.jade b/client/components/activities/activities.jade index d3e3d5ba..bddc4dad 100644 --- a/client/components/activities/activities.jade +++ b/client/components/activities/activities.jade @@ -14,6 +14,9 @@ template(name="boardActivities") p.activity-desc +memberName(user=user) + if($eq activityType 'deleteAttachment') + | {{{_ 'activity-delete-attach' cardLink}}}. + if($eq activityType 'addAttachment') | {{{_ 'activity-attached' attachmentLink cardLink}}}. @@ -31,12 +34,28 @@ template(name="boardActivities") .activity-checklist(href="{{ card.absoluteUrl }}") +viewer = checklist.title + if($eq activityType 'removeChecklist') + | {{{_ 'activity-checklist-removed' cardLink}}}. + + if($eq activityType 'checkedItem') + | {{{_ 'activity-checked-item' checkItem checklist.title cardLink}}}. + + if($eq activityType 'uncheckedItem') + | {{{_ 'activity-unchecked-item' checkItem checklist.title cardLink}}}. + + if($eq activityType 'checklistCompleted') + | {{{_ 'activity-checklist-completed' checklist.title cardLink}}}. + + if($eq activityType 'checklistUncompleted') + | {{{_ 'activity-checklist-uncompleted' checklist.title cardLink}}}. if($eq activityType 'addChecklistItem') | {{{_ 'activity-checklist-item-added' checklist.title cardLink}}}. .activity-checklist(href="{{ card.absoluteUrl }}") +viewer = checklistItem.title + if($eq activityType 'removedChecklistItem') + | {{{_ 'activity-checklist-item-removed' checklist.title cardLink}}}. if($eq activityType 'archivedCard') | {{{_ 'activity-archived' cardLink}}}. @@ -89,6 +108,12 @@ template(name="boardActivities") if($eq activityType 'restoredCard') | {{{_ 'activity-sent' cardLink boardLabel}}}. + if($eq activityType 'addedLabel') + | {{{_ 'activity-added-label' lastLabel cardLink}}}. + + if($eq activityType 'removedLabel') + | {{{_ 'activity-removed-label' lastLabel cardLink}}}. + if($eq activityType 'unjoinMember') if($eq user._id member._id) | {{{_ 'activity-unjoined' cardLink}}}. @@ -119,6 +144,28 @@ template(name="cardActivities") | {{{_ 'activity-removed' cardLabel memberLink}}}. if($eq activityType 'archivedCard') | {{_ 'activity-archived' cardLabel}}. + + if($eq activityType 'addedLabel') + | {{{_ 'activity-added-label-card' lastLabel }}}. + + if($eq activityType 'removedLabel') + | {{{_ 'activity-removed-label-card' lastLabel }}}. + + if($eq activityType 'removeChecklist') + | {{{_ 'activity-checklist-removed' cardLabel}}}. + + if($eq activityType 'checkedItem') + | {{{_ 'activity-checked-item-card' checkItem checklist.title }}}. + + if($eq activityType 'uncheckedItem') + | {{{_ 'activity-unchecked-item-card' checkItem checklist.title }}}. + + if($eq activityType 'checklistCompleted') + | {{{_ 'activity-checklist-completed-card' checklist.title }}}. + + if($eq activityType 'checklistUncompleted') + | {{{_ 'activity-checklist-uncompleted-card' checklist.title }}}. + if($eq activityType 'restoredCard') | {{_ 'activity-sent' cardLabel boardLabel}}. if($eq activityType 'moveCard') @@ -127,6 +174,10 @@ template(name="cardActivities") | {{{_ 'activity-attached' attachmentLink cardLabel}}}. if attachment.isImage img.attachment-image-preview(src=attachment.url) + if($eq activityType 'deleteAttachment') + | {{{_ 'activity-delete-attach' cardLabel}}}. + if($eq activityType 'removedChecklist') + | {{{_ 'activity-checklist-removed' cardLabel}}}. if($eq activityType 'addChecklist') | {{{_ 'activity-checklist-added' cardLabel}}}. .activity-checklist diff --git a/client/components/activities/activities.js b/client/components/activities/activities.js index 25e151fd..b3fe8f50 100644 --- a/client/components/activities/activities.js +++ b/client/components/activities/activities.js @@ -50,6 +50,12 @@ BlazeComponent.extendComponent({ } }, + checkItem(){ + const checkItemId = this.currentData().checklistItemId; + const checkItem = ChecklistItems.findOne({_id:checkItemId}); + return checkItem.title; + }, + boardLabel() { return TAPi18n.__('this-board'); }, @@ -66,6 +72,16 @@ BlazeComponent.extendComponent({ }, card.title)); }, + lastLabel(){ + const lastLabelId = this.currentData().labelId; + const lastLabel = Boards.findOne(Session.get('currentBoard')).getLabelById(lastLabelId); + if(lastLabel.name === undefined || lastLabel.name === ''){ + return lastLabel.color; + }else{ + return lastLabel.name; + } + }, + listLabel() { return this.currentData().list().title; }, diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index 1c6c8f8c..75b2f02b 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -87,6 +87,10 @@ template(name="boardHeaderBar") if Filter.isActive a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}") i.fa.fa-times-thin + if currentUser.isAdmin + a.board-header-btn.js-open-rules-view(title="{{_ 'rules'}}") + i.fa.fa-magic + span {{_ 'rules'}} a.board-header-btn.js-open-search-view(title="{{_ 'search'}}") i.fa.fa-search @@ -290,6 +294,11 @@ template(name="boardChangeTitlePopup") textarea.js-board-desc= description input.primary.wide(type="submit" value="{{_ 'rename'}}") +template(name="boardCreateRulePopup") + p {{_ 'close-board-pop'}} + button.js-confirm.negate.full(type="submit") {{_ 'archive'}} + + template(name="archiveBoardPopup") p {{_ 'close-board-pop'}} button.js-confirm.negate.full(type="submit") {{_ 'archive'}} diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index 2dfd58c1..89f686ab 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -108,6 +108,9 @@ BlazeComponent.extendComponent({ 'click .js-open-search-view'() { Sidebar.setView('search'); }, + 'click .js-open-rules-view'() { + Modal.openWide('rulesMain'); + }, 'click .js-multiselection-activate'() { const currentCard = Session.get('currentCard'); MultiSelection.activate(); diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index d1bb3a1e..da0f126a 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -110,7 +110,7 @@ BlazeComponent.extendComponent({ }, onRendered() { - if (!Utils.isMiniScreen()){ + if (!Utils.isMiniScreen()) { Meteor.setTimeout(() => { this.scrollParentContainer(); }, 500); diff --git a/client/components/forms/forms.styl b/client/components/forms/forms.styl index 5be70b7a..892a6e74 100644 --- a/client/components/forms/forms.styl +++ b/client/components/forms/forms.styl @@ -1,5 +1,6 @@ @import 'nib' +select, textarea, input:not([type=file]), button diff --git a/client/components/main/layouts.jade b/client/components/main/layouts.jade index bd9fac23..68876dc5 100644 --- a/client/components/main/layouts.jade +++ b/client/components/main/layouts.jade @@ -18,6 +18,10 @@ template(name="userFormsLayout") img(src="{{pathFor '/wekan-logo.png'}}" alt="Wekan") section.auth-dialog +Template.dynamic(template=content) + +connectionMethod + if isCas + .at-form + button#cas(class='at-btn submit' type='submit') {{casSignInLabel}} div.at-form-lang select.select-lang.js-userform-set-language each languages @@ -33,11 +37,18 @@ template(name="defaultLayout") if (Modal.isOpen) #modal .overlay - .modal-content - a.modal-close-btn.js-close-modal - i.fa.fa-times-thin - +Template.dynamic(template=Modal.getHeaderName) - +Template.dynamic(template=Modal.getTemplateName) + if (Modal.isWide) + .modal-content-wide.modal-container + a.modal-close-btn.js-close-modal + i.fa.fa-times-thin + +Template.dynamic(template=Modal.getHeaderName) + +Template.dynamic(template=Modal.getTemplateName) + else + .modal-content.modal-container + a.modal-close-btn.js-close-modal + i.fa.fa-times-thin + +Template.dynamic(template=Modal.getHeaderName) + +Template.dynamic(template=Modal.getTemplateName) template(name="notFound") +message(label='page-not-found') diff --git a/client/components/main/layouts.js b/client/components/main/layouts.js index f12718a7..393f890b 100644 --- a/client/components/main/layouts.js +++ b/client/components/main/layouts.js @@ -6,7 +6,23 @@ const i18nTagToT9n = (i18nTag) => { return i18nTag; }; +const validator = { + set(obj, prop, value) { + if (prop === 'state' && value !== 'signIn') { + $('.at-form-authentication').hide(); + } else if (prop === 'state' && value === 'signIn') { + $('.at-form-authentication').show(); + } + // The default behavior to store the value + obj[prop] = value; + // Indicate success + return true; + }, +}; + Template.userFormsLayout.onRendered(() => { + AccountsTemplates.state.form.keys = new Proxy(AccountsTemplates.state.form.keys, validator); + const i18nTag = navigator.language; if (i18nTag) { T9n.setLanguage(i18nTagToT9n(i18nTag)); @@ -39,6 +55,17 @@ Template.userFormsLayout.helpers({ const curLang = T9n.getLanguage() || 'en'; return t9nTag === curLang; }, +/* + isCas() { + return Meteor.settings.public && + Meteor.settings.public.cas && + Meteor.settings.public.cas.loginUrl; + }, + + casSignInLabel() { + return TAPi18n.__('casSignIn', {}, T9n.getLanguage() || 'en'); + }, +*/ }); Template.userFormsLayout.events({ @@ -47,6 +74,51 @@ Template.userFormsLayout.events({ T9n.setLanguage(i18nTagToT9n(i18nTag)); evt.preventDefault(); }, + 'click button#cas'() { + Meteor.loginWithCas(function() { + if (FlowRouter.getRouteName() === 'atSignIn') { + FlowRouter.go('/'); + } + }); + }, + 'click #at-btn'(event) { + /* All authentication method can be managed/called here. + !! DON'T FORGET to correctly fill the fields of the user during its creation if necessary authenticationMethod : String !! + */ + const authenticationMethodSelected = $('.select-authentication').val(); + // Local account + if (authenticationMethodSelected === 'password') { + return; + } + + // Stop submit #at-pwd-form + event.preventDefault(); + event.stopImmediatePropagation(); + + const email = $('#at-field-username_and_email').val(); + const password = $('#at-field-password').val(); + + // Ldap account + if (authenticationMethodSelected === 'ldap') { + // Check if the user can use the ldap connection + Meteor.subscribe('user-authenticationMethod', email, { + onReady() { + const user = Users.findOne(); + if (user === undefined || user.authenticationMethod === 'ldap') { + // Use the ldap connection package + Meteor.loginWithLDAP(email, password, function(error) { + if (!error) { + // Connection + return FlowRouter.go('/'); + } + return error; + }); + } + return this.stop(); + }, + }); + } + }, }); Template.defaultLayout.events({ diff --git a/client/components/main/layouts.styl b/client/components/main/layouts.styl index a79ff337..3457a028 100644 --- a/client/components/main/layouts.styl +++ b/client/components/main/layouts.styl @@ -61,6 +61,23 @@ body display: block float: right font-size: 24px + + .modal-content-wide + width: 800px + min-height: 0px + margin: 42px auto + padding: 12px + border-radius: 4px + background: darken(white, 13%) + z-index: 110 + + h2 + margin-bottom: 25px + + .modal-close-btn + display: block + float: right + font-size: 24px h1 font-size: 22px diff --git a/client/components/rules/.DS_Store b/client/components/rules/.DS_Store Binary files differnew file mode 100644 index 00000000..5008ddfc --- /dev/null +++ b/client/components/rules/.DS_Store diff --git a/client/components/rules/actions/boardActions.jade b/client/components/rules/actions/boardActions.jade new file mode 100644 index 00000000..768d77cf --- /dev/null +++ b/client/components/rules/actions/boardActions.jade @@ -0,0 +1,46 @@ +template(name="boardActions") + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-move-card-to'}} + div.trigger-dropdown + select(id="move-gen-action") + option(value="top") {{_'r-top-of'}} + option(value="bottom") {{_'r-bottom-of'}} + div.trigger-text + | {{_'r-its-list'}} + div.trigger-button.js-add-gen-move-action.js-goto-rules + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-move-card-to'}} + div.trigger-dropdown + select(id="move-spec-action") + option(value="top") {{_'r-top-of'}} + option(value="bottom") {{_'r-bottom-of'}} + div.trigger-text + | {{_'r-list'}} + div.trigger-dropdown + input(id="listName",type=text,placeholder="{{_'r-name'}}") + div.trigger-button.js-add-spec-move-action.js-goto-rules + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-dropdown + select(id="arch-action") + option(value="archive") {{_'r-archive'}} + option(value="unarchive") {{_'r-unarchive'}} + div.trigger-text + | {{_'r-card'}} + div.trigger-button.js-add-arch-action.js-goto-rules + i.fa.fa-plus + + + + + + + diff --git a/client/components/rules/actions/boardActions.js b/client/components/rules/actions/boardActions.js new file mode 100644 index 00000000..95771fce --- /dev/null +++ b/client/components/rules/actions/boardActions.js @@ -0,0 +1,122 @@ +BlazeComponent.extendComponent({ + onCreated() { + + }, + + events() { + return [{ + 'click .js-add-spec-move-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const actionSelected = this.find('#move-spec-action').value; + const listTitle = this.find('#listName').value; + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + if (actionSelected === 'top') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'moveCardToTop', + listTitle, + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + if (actionSelected === 'bottom') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'moveCardToBottom', + listTitle, + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + }, + 'click .js-add-gen-move-action' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const boardId = Session.get('currentBoard'); + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const actionSelected = this.find('#move-gen-action').value; + if (actionSelected === 'top') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'moveCardToTop', + 'listTitle': '*', + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + if (actionSelected === 'bottom') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'moveCardToBottom', + 'listTitle': '*', + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + }, + 'click .js-add-arch-action' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const boardId = Session.get('currentBoard'); + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const actionSelected = this.find('#arch-action').value; + if (actionSelected === 'archive') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'archive', + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + if (actionSelected === 'unarchive') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'unarchive', + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + }, + }]; + }, + +}).register('boardActions'); +/* eslint-no-undef */ diff --git a/client/components/rules/actions/cardActions.jade b/client/components/rules/actions/cardActions.jade new file mode 100644 index 00000000..74ad9ab5 --- /dev/null +++ b/client/components/rules/actions/cardActions.jade @@ -0,0 +1,43 @@ +template(name="cardActions") + div.trigger-item + div.trigger-content + div.trigger-dropdown + select(id="label-action") + option(value="add") {{{_'r-add'}}} + option(value="remove") {{{_'r-remove'}}} + div.trigger-text + | {{{_'r-label'}}} + div.trigger-dropdown + select(id="label-id") + each labels + option(value="#{_id}") + = name + div.trigger-button.js-add-label-action.js-goto-rules + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-dropdown + select(id="member-action") + option(value="add") {{{_'r-add'}}} + option(value="remove") {{{_'r-remove'}}} + div.trigger-text + | {{{_'r-member'}}} + div.trigger-dropdown + input(id="member-name",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-button.js-add-member-action.js-goto-rules + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{{_'r-remove-all'}}} + div.trigger-button.js-add-removeall-action.js-goto-rules + i.fa.fa-plus + + + + + + + diff --git a/client/components/rules/actions/cardActions.js b/client/components/rules/actions/cardActions.js new file mode 100644 index 00000000..b04440bd --- /dev/null +++ b/client/components/rules/actions/cardActions.js @@ -0,0 +1,118 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.subscribe('allRules'); + }, + + labels() { + const labels = Boards.findOne(Session.get('currentBoard')).labels; + for (let i = 0; i < labels.length; i++) { + if (labels[i].name === '' || labels[i].name === undefined) { + labels[i].name = labels[i].color.toUpperCase(); + } + } + return labels; + }, + + events() { + return [{ + 'click .js-add-label-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const actionSelected = this.find('#label-action').value; + const labelId = this.find('#label-id').value; + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + if (actionSelected === 'add') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'addLabel', + labelId, + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + if (actionSelected === 'remove') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'removeLabel', + labelId, + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + + }, + 'click .js-add-member-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const actionSelected = this.find('#member-action').value; + const username = this.find('#member-name').value; + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + if (actionSelected === 'add') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'addMember', + username, + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + desc, + }); + } + if (actionSelected === 'remove') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'removeMember', + username, + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + }, + 'click .js-add-removeall-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const triggerId = Triggers.insert(trigger); + const desc = Utils.getTriggerActionDesc(event, this); + const boardId = Session.get('currentBoard'); + const actionId = Actions.insert({ + actionType: 'removeMember', + 'username': '*', + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + }, + }]; + }, + +}).register('cardActions'); diff --git a/client/components/rules/actions/checklistActions.jade b/client/components/rules/actions/checklistActions.jade new file mode 100644 index 00000000..8414a1a5 --- /dev/null +++ b/client/components/rules/actions/checklistActions.jade @@ -0,0 +1,51 @@ +template(name="checklistActions") + div.trigger-item + div.trigger-content + div.trigger-dropdown + select(id="check-action") + option(value="add") {{{_'r-add'}}} + option(value="remove") {{{_'r-remove'}}} + div.trigger-text + | {{{_'r-checklist'}}} + div.trigger-dropdown + input(id="checklist-name",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-button.js-add-checklist-action.js-goto-rules + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-dropdown + select(id="checkall-action") + option(value="check") {{{_'r-check-all'}}} + option(value="uncheck") {{{_'r-uncheck-all'}}} + div.trigger-text + | {{{_'r-items-check'}}} + div.trigger-dropdown + input(id="checklist-name2",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-button.js-add-checkall-action.js-goto-rules + i.fa.fa-plus + + + div.trigger-item + div.trigger-content + div.trigger-dropdown + select(id="check-item-action") + option(value="check") {{{_'r-check'}}} + option(value="uncheck") {{{_'r-uncheck'}}} + div.trigger-text + | {{{_'r-item'}}} + div.trigger-dropdown + input(id="checkitem-name",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-text + | {{{_'r-of-checklist'}}} + div.trigger-dropdown + input(id="checklist-name3",type=text,placeholder="{{{_'r-name'}}}") + div.trigger-button.js-add-check-item-action.js-goto-rules + i.fa.fa-plus + + + + + + + diff --git a/client/components/rules/actions/checklistActions.js b/client/components/rules/actions/checklistActions.js new file mode 100644 index 00000000..4b70f959 --- /dev/null +++ b/client/components/rules/actions/checklistActions.js @@ -0,0 +1,128 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.subscribe('allRules'); + }, + events() { + return [{ + 'click .js-add-checklist-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const actionSelected = this.find('#check-action').value; + const checklistName = this.find('#checklist-name').value; + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + if (actionSelected === 'add') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'addChecklist', + checklistName, + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + if (actionSelected === 'remove') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'removeChecklist', + checklistName, + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + + }, + 'click .js-add-checkall-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const actionSelected = this.find('#checkall-action').value; + const checklistName = this.find('#checklist-name2').value; + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + if (actionSelected === 'check') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'checkAll', + checklistName, + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + if (actionSelected === 'uncheck') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'uncheckAll', + checklistName, + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + }, + 'click .js-add-check-item-action' (event) { + const ruleName = this.data().ruleName.get(); + const trigger = this.data().triggerVar.get(); + const checkItemName = this.find('#checkitem-name'); + const checklistName = this.find('#checklist-name3'); + const actionSelected = this.find('#check-item-action').value; + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + if (actionSelected === 'check') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'checkItem', + checklistName, + checkItemName, + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + if (actionSelected === 'uncheck') { + const triggerId = Triggers.insert(trigger); + const actionId = Actions.insert({ + actionType: 'uncheckItem', + checklistName, + checkItemName, + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + } + }, + }]; + }, + +}).register('checklistActions'); diff --git a/client/components/rules/actions/mailActions.jade b/client/components/rules/actions/mailActions.jade new file mode 100644 index 00000000..7be78c75 --- /dev/null +++ b/client/components/rules/actions/mailActions.jade @@ -0,0 +1,11 @@ +template(name="mailActions") + div.trigger-item.trigger-item-mail + div.trigger-content.trigger-content-mail + div.trigger-text.trigger-text-email + | {{_'r-send-email'}} + div.trigger-dropdown-mail + input(id="email-to",type=text,placeholder="{{_'r-to'}}") + input(id="email-subject",type=text,placeholder="{{_'r-subject'}}") + textarea(id="email-msg") + div.trigger-button.trigger-button-email.js-mail-action.js-goto-rules + i.fa.fa-plus diff --git a/client/components/rules/actions/mailActions.js b/client/components/rules/actions/mailActions.js new file mode 100644 index 00000000..40cbc280 --- /dev/null +++ b/client/components/rules/actions/mailActions.js @@ -0,0 +1,35 @@ +BlazeComponent.extendComponent({ + onCreated() { + + }, + + events() { + return [{ + 'click .js-mail-action' (event) { + const emailTo = this.find('#email-to').value; + const emailSubject = this.find('#email-subject').value; + const emailMsg = this.find('#email-msg').value; + const trigger = this.data().triggerVar.get(); + const ruleName = this.data().ruleName.get(); + const triggerId = Triggers.insert(trigger); + const boardId = Session.get('currentBoard'); + const desc = Utils.getTriggerActionDesc(event, this); + const actionId = Actions.insert({ + actionType: 'sendEmail', + emailTo, + emailSubject, + emailMsg, + boardId, + desc, + }); + Rules.insert({ + title: ruleName, + triggerId, + actionId, + boardId, + }); + }, + }]; + }, + +}).register('mailActions'); diff --git a/client/components/rules/ruleDetails.jade b/client/components/rules/ruleDetails.jade new file mode 100644 index 00000000..7183cf96 --- /dev/null +++ b/client/components/rules/ruleDetails.jade @@ -0,0 +1,20 @@ +template(name="ruleDetails") + .rules + h2 + i.fa.fa-magic + | {{{_ 'r-rule-details' }}} + .triggers-content + .triggers-body + .triggers-main-body + div.trigger-item + div.trigger-content + div.trigger-text + = trigger + div.trigger-item + div.trigger-content + div.trigger-text + = action + div.rules-back + button.js-goback + i.fa.fa-chevron-left + | {{{_ 'back'}}} diff --git a/client/components/rules/ruleDetails.js b/client/components/rules/ruleDetails.js new file mode 100644 index 00000000..17c86dc3 --- /dev/null +++ b/client/components/rules/ruleDetails.js @@ -0,0 +1,38 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.subscribe('allRules'); + this.subscribe('allTriggers'); + this.subscribe('allActions'); + + }, + + trigger() { + const ruleId = this.data().ruleId; + const rule = Rules.findOne({ + _id: ruleId.get(), + }); + const trigger = Triggers.findOne({ + _id: rule.triggerId, + }); + const desc = trigger.description(); + const upperdesc = desc.charAt(0).toUpperCase() + desc.substr(1); + return upperdesc; + }, + action() { + const ruleId = this.data().ruleId; + const rule = Rules.findOne({ + _id: ruleId.get(), + }); + const action = Actions.findOne({ + _id: rule.actionId, + }); + const desc = action.description(); + const upperdesc = desc.charAt(0).toUpperCase() + desc.substr(1); + return upperdesc; + }, + + events() { + return [{}]; + }, + +}).register('ruleDetails'); diff --git a/client/components/rules/rules.styl b/client/components/rules/rules.styl new file mode 100644 index 00000000..b52f84a7 --- /dev/null +++ b/client/components/rules/rules.styl @@ -0,0 +1,167 @@ +.rules-list + overflow:hidden + overflow-y:scroll + max-height: 400px +.rules-lists-item + display: block + position: relative + overflow: auto + p + display: inline-block + float: left + margin: revert + +.rules-btns-group + position: absolute + right: 0 + top: 50% + transform: translateY(-50%) + button + margin: auto +.rules-add + display: block + overflow: auto + margin-top: 15px + margin-bottom: 5px + input + display: inline-block + float: right + margin: auto + margin-right: 10px + button + display: inline-block + float: right + margin: auto +.rules-back + display: block + overflow: auto + margin-top: 15px + margin-bottom: 5px + button + display: inline-block + float: right + margin: auto + margin-right:14px + +.flex + display: -webkit-box + display: -moz-box + display: -webkit-flex + display: -moz-flex + display: -ms-flexbox + display: flex + + + +.triggers-content + color: #727479 + background: #dedede + .triggers-body + display flex + padding-top 15px + height 100% + + .triggers-side-menu + background-color: #f7f7f7 + border: 1px solid #f0f0f0 + border-radius: 4px + height: intrinsic + box-shadow: inset -1px -1px 3px rgba(0,0,0,.05) + + ul + + li + margin: 0.1rem 0.2rem; + width:50px + height:50px + text-align:center + font-size: 25px + position: relative + + i + position: absolute; + top: 50%; + left: 50%; + box-shadow: none + transform: translate(-50%,-50%); + + + &.active + background #fff + box-shadow 0 1px 2px rgba(0,0,0,0.15); + + &:hover + background #fff + box-shadow 0 1px 2px rgba(0,0,0,0.15); + a + @extends .flex + padding: 1rem 0 1rem 1rem + width: 100% - 5rem + + + span + font-size: 13px + .triggers-main-body + padding: 0.1em 1em + width:100% + .trigger-item + overflow:auto + padding:10px + height:40px + margin-bottom:5px + border-radius: 3px + position: relative + background-color: white + .trigger-content + position:absolute + top:50% + transform: translateY(-50%) + left:10px + .trigger-text + font-size: 16px + display:inline-block + .trigger-text.trigger-text-email + margin-left: 5px; + margin-top: 10px; + margin-bottom: 10px; + .trigger-dropdown + display:inline-block + select + width:auto + height:30px + margin:0px + margin-left:5px + input + display: inline-block + width: 80px; + margin: 0; + .trigger-content-mail + left:20px + right:100px + .trigger-button + position:absolute + top:50% + transform: translateY(-50%) + width:30px + height:30px + border: 1px solid #eee + border-radius: 4px + box-shadow: inset -1px -1px 3px rgba(0,0,0,.05) + text-align:center + font-size: 20px + right:10px + i + position: absolute + top: 50% + left: 50% + box-shadow: none + transform: translate(-50%,-50%) + &:hover, &.is-active + box-shadow: 0 0 0 2px darken(white, 60%) inset + .trigger-button.trigger-button-email + top:30px + .trigger-item.trigger-item-mail + height:300px + + + diff --git a/client/components/rules/rulesActions.jade b/client/components/rules/rulesActions.jade new file mode 100644 index 00000000..3ac04e1c --- /dev/null +++ b/client/components/rules/rulesActions.jade @@ -0,0 +1,29 @@ +template(name="rulesActions") + h2 + i.fa.fa-magic + | {{{_ 'r-rule' }}} "#{data.ruleName.get}" - {{{_ 'r-add-action'}}} + .triggers-content + .triggers-body + .triggers-side-menu + ul + li.active.js-set-board-actions + i.fa.fa-columns + li.js-set-card-actions + i.fa.fa-sticky-note + li.js-set-checklist-actions + i.fa.fa-check + li.js-set-mail-actions + i.fa.fa-at + .triggers-main-body + if ($eq currentActions.get 'board') + +boardActions(ruleName=data.ruleName triggerVar=data.triggerVar) + else if ($eq currentActions.get 'card') + +cardActions(ruleName=data.ruleName triggerVar=data.triggerVar) + else if ($eq currentActions.get 'checklist') + +checklistActions(ruleName=data.ruleName triggerVar=data.triggerVar) + else if ($eq currentActions.get 'mail') + +mailActions(ruleName=data.ruleName triggerVar=data.triggerVar) + div.rules-back + button.js-goback + i.fa.fa-chevron-left + | {{{_ 'back'}}} diff --git a/client/components/rules/rulesActions.js b/client/components/rules/rulesActions.js new file mode 100644 index 00000000..64a5c70e --- /dev/null +++ b/client/components/rules/rulesActions.js @@ -0,0 +1,58 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.currentActions = new ReactiveVar('board'); + }, + + setBoardActions() { + this.currentActions.set('board'); + $('.js-set-card-actions').removeClass('active'); + $('.js-set-board-actions').addClass('active'); + $('.js-set-checklist-actions').removeClass('active'); + $('.js-set-mail-actions').removeClass('active'); + }, + setCardActions() { + this.currentActions.set('card'); + $('.js-set-card-actions').addClass('active'); + $('.js-set-board-actions').removeClass('active'); + $('.js-set-checklist-actions').removeClass('active'); + $('.js-set-mail-actions').removeClass('active'); + }, + setChecklistActions() { + this.currentActions.set('checklist'); + $('.js-set-card-actions').removeClass('active'); + $('.js-set-board-actions').removeClass('active'); + $('.js-set-checklist-actions').addClass('active'); + $('.js-set-mail-actions').removeClass('active'); + }, + setMailActions() { + this.currentActions.set('mail'); + $('.js-set-card-actions').removeClass('active'); + $('.js-set-board-actions').removeClass('active'); + $('.js-set-checklist-actions').removeClass('active'); + $('.js-set-mail-actions').addClass('active'); + }, + + rules() { + return Rules.find({}); + }, + + name() { + // console.log(this.data()); + }, + events() { + return [{ + 'click .js-set-board-actions'(){ + this.setBoardActions(); + }, + 'click .js-set-card-actions'() { + this.setCardActions(); + }, + 'click .js-set-mail-actions'() { + this.setMailActions(); + }, + 'click .js-set-checklist-actions'() { + this.setChecklistActions(); + }, + }]; + }, +}).register('rulesActions'); diff --git a/client/components/rules/rulesList.jade b/client/components/rules/rulesList.jade new file mode 100644 index 00000000..c2676aa7 --- /dev/null +++ b/client/components/rules/rulesList.jade @@ -0,0 +1,27 @@ +template(name="rulesList") + .rules + h2 + i.fa.fa-magic + | {{{_ 'r-board-rules' }}} + + ul.rules-list + each rules + li.rules-lists-item + p + = title + div.rules-btns-group + button.js-goto-details + i.fa.fa-eye + | {{{_ 'r-view-rule'}}} + if currentUser.isAdmin + button.js-delete-rule + i.fa.fa-trash-o + | {{{_ 'r-delete-rule'}}} + else + li.no-items-message {{{_ 'r-no-rules' }}} + if currentUser.isAdmin + div.rules-add + button.js-goto-trigger + i.fa.fa-plus + | {{{_ 'r-add-rule'}}} + input(type=text,placeholder="{{{_ 'r-new-rule-name' }}}",id="ruleTitle")
\ No newline at end of file diff --git a/client/components/rules/rulesList.js b/client/components/rules/rulesList.js new file mode 100644 index 00000000..d3923bf9 --- /dev/null +++ b/client/components/rules/rulesList.js @@ -0,0 +1,15 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.subscribe('allRules'); + }, + + rules() { + const boardId = Session.get('currentBoard'); + return Rules.find({ + boardId, + }); + }, + events() { + return [{}]; + }, +}).register('rulesList'); diff --git a/client/components/rules/rulesMain.jade b/client/components/rules/rulesMain.jade new file mode 100644 index 00000000..dc33ee4e --- /dev/null +++ b/client/components/rules/rulesMain.jade @@ -0,0 +1,9 @@ +template(name="rulesMain") + if($eq rulesCurrentTab.get 'rulesList') + +rulesList + if($eq rulesCurrentTab.get 'trigger') + +rulesTriggers(ruleName=ruleName triggerVar=triggerVar) + if($eq rulesCurrentTab.get 'action') + +rulesActions(ruleName=ruleName triggerVar=triggerVar) + if($eq rulesCurrentTab.get 'ruleDetails') + +ruleDetails(ruleId=ruleId)
\ No newline at end of file diff --git a/client/components/rules/rulesMain.js b/client/components/rules/rulesMain.js new file mode 100644 index 00000000..0752a541 --- /dev/null +++ b/client/components/rules/rulesMain.js @@ -0,0 +1,70 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.rulesCurrentTab = new ReactiveVar('rulesList'); + this.ruleName = new ReactiveVar(''); + this.triggerVar = new ReactiveVar(); + this.ruleId = new ReactiveVar(); + }, + + setTrigger() { + this.rulesCurrentTab.set('trigger'); + }, + + setRulesList() { + this.rulesCurrentTab.set('rulesList'); + }, + + setAction() { + this.rulesCurrentTab.set('action'); + }, + + setRuleDetails() { + this.rulesCurrentTab.set('ruleDetails'); + }, + + events() { + return [{ + 'click .js-delete-rule' () { + const rule = this.currentData(); + Rules.remove(rule._id); + Actions.remove(rule.actionId); + Triggers.remove(rule.triggerId); + + }, + 'click .js-goto-trigger' (event) { + event.preventDefault(); + const ruleTitle = this.find('#ruleTitle').value; + if(ruleTitle !== undefined && ruleTitle !== ''){ + this.find('#ruleTitle').value = ''; + this.ruleName.set(ruleTitle); + this.setTrigger(); + } + }, + 'click .js-goto-action' (event) { + event.preventDefault(); + this.setAction(); + }, + 'click .js-goto-rules' (event) { + event.preventDefault(); + this.setRulesList(); + }, + 'click .js-goback' (event) { + event.preventDefault(); + if(this.rulesCurrentTab.get() === 'trigger' || this.rulesCurrentTab.get() === 'ruleDetails' ){ + this.setRulesList(); + } + if(this.rulesCurrentTab.get() === 'action'){ + this.setTrigger(); + } + }, + 'click .js-goto-details' (event) { + event.preventDefault(); + const rule = this.currentData(); + this.ruleId.set(rule._id); + this.setRuleDetails(); + }, + + }]; + }, + +}).register('rulesMain'); diff --git a/client/components/rules/rulesTriggers.jade b/client/components/rules/rulesTriggers.jade new file mode 100644 index 00000000..79d9d98e --- /dev/null +++ b/client/components/rules/rulesTriggers.jade @@ -0,0 +1,25 @@ +template(name="rulesTriggers") + h2 + i.fa.fa-magic + | {{{_ 'r-rule' }}} "#{data.ruleName.get}" - {{{_ 'r-add-trigger'}}} + .triggers-content + .triggers-body + .triggers-side-menu + ul + li.active.js-set-board-triggers + i.fa.fa-columns + li.js-set-card-triggers + i.fa.fa-sticky-note + li.js-set-checklist-triggers + i.fa.fa-check + .triggers-main-body + if showBoardTrigger.get + +boardTriggers + else if showCardTrigger.get + +cardTriggers + else if showChecklistTrigger.get + +checklistTriggers + div.rules-back + button.js-goback + i.fa.fa-chevron-left + | {{{_ 'back'}}} diff --git a/client/components/rules/rulesTriggers.js b/client/components/rules/rulesTriggers.js new file mode 100644 index 00000000..e3c16221 --- /dev/null +++ b/client/components/rules/rulesTriggers.js @@ -0,0 +1,53 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.showBoardTrigger = new ReactiveVar(true); + this.showCardTrigger = new ReactiveVar(false); + this.showChecklistTrigger = new ReactiveVar(false); + }, + + setBoardTriggers() { + this.showBoardTrigger.set(true); + this.showCardTrigger.set(false); + this.showChecklistTrigger.set(false); + $('.js-set-card-triggers').removeClass('active'); + $('.js-set-board-triggers').addClass('active'); + $('.js-set-checklist-triggers').removeClass('active'); + }, + setCardTriggers() { + this.showBoardTrigger.set(false); + this.showCardTrigger.set(true); + this.showChecklistTrigger.set(false); + $('.js-set-card-triggers').addClass('active'); + $('.js-set-board-triggers').removeClass('active'); + $('.js-set-checklist-triggers').removeClass('active'); + }, + setChecklistTriggers() { + this.showBoardTrigger.set(false); + this.showCardTrigger.set(false); + this.showChecklistTrigger.set(true); + $('.js-set-card-triggers').removeClass('active'); + $('.js-set-board-triggers').removeClass('active'); + $('.js-set-checklist-triggers').addClass('active'); + }, + + rules() { + return Rules.find({}); + }, + + name() { + // console.log(this.data()); + }, + events() { + return [{ + 'click .js-set-board-triggers' () { + this.setBoardTriggers(); + }, + 'click .js-set-card-triggers' () { + this.setCardTriggers(); + }, + 'click .js-set-checklist-triggers' () { + this.setChecklistTriggers(); + }, + }]; + }, +}).register('rulesTriggers'); diff --git a/client/components/rules/triggers/boardTriggers.jade b/client/components/rules/triggers/boardTriggers.jade new file mode 100644 index 00000000..48b9345c --- /dev/null +++ b/client/components/rules/triggers/boardTriggers.jade @@ -0,0 +1,68 @@ +template(name="boardTriggers") + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-a-card-is'}} + div.trigger-dropdown + select(id="gen-action") + option(value="created") {{_'r-added-to'}} + option(value="removed") {{_'r-removed-from'}} + div.trigger-text + | {{_'r-the-board'}} + div.trigger-button.js-add-gen-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-a-card-is'}} + div.trigger-dropdown + select(id="create-action") + option(value="created") {{_'r-added-to'}} + option(value="removed") {{_'r-removed-from'}} + div.trigger-text + | {{_'r-list'}} + div.trigger-dropdown + input(id="create-list-name",type=text,placeholder="{{_'r-list-name'}}") + div.trigger-button.js-add-create-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-a-card-is-moved'}} + div.trigger-button.js-add-gen-moved-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-a-card-is'}} + div.trigger-dropdown + select(id="move-action") + option(value="moved-to") {{_'r-moved-to'}} + option(value="moved-from") {{_'r-moved-from'}} + div.trigger-text + | {{_'r-list'}} + div.trigger-dropdown + input(id="move-list-name",type=text,placeholder="{{_'r-list-name'}}") + div.trigger-button.js-add-moved-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-a-card-is'}} + div.trigger-dropdown + select(id="arch-action") + option(value="archived") {{_'r-archived'}} + option(value="unarchived") {{_'r-unarchived'}} + div.trigger-button.js-add-arch-trigger.js-goto-action + i.fa.fa-plus + + + + + + + diff --git a/client/components/rules/triggers/boardTriggers.js b/client/components/rules/triggers/boardTriggers.js new file mode 100644 index 00000000..40c5b07e --- /dev/null +++ b/client/components/rules/triggers/boardTriggers.js @@ -0,0 +1,116 @@ +BlazeComponent.extendComponent({ + onCreated() { + + }, + + events() { + return [{ + 'click .js-add-gen-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const datas = this.data(); + const actionSelected = this.find('#gen-action').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'created') { + datas.triggerVar.set({ + activityType: 'createCard', + boardId, + 'listName': '*', + desc, + }); + } + if (actionSelected === 'removed') { + datas.triggerVar.set({ + activityType: 'removeCard', + boardId, + desc, + }); + } + + }, + 'click .js-add-create-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const datas = this.data(); + const actionSelected = this.find('#create-action').value; + const listName = this.find('#create-list-name').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'created') { + datas.triggerVar.set({ + activityType: 'createCard', + boardId, + listName, + desc, + }); + } + if (actionSelected === 'removed') { + datas.triggerVar.set({ + activityType: 'removeCard', + boardId, + listName, + desc, + }); + } + }, + 'click .js-add-moved-trigger' (event) { + const datas = this.data(); + const desc = Utils.getTriggerActionDesc(event, this); + + const actionSelected = this.find('#move-action').value; + const listName = this.find('#move-list-name').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'moved-to') { + datas.triggerVar.set({ + activityType: 'moveCard', + boardId, + listName, + 'oldListName': '*', + desc, + }); + } + if (actionSelected === 'moved-from') { + datas.triggerVar.set({ + activityType: 'moveCard', + boardId, + 'listName': '*', + 'oldListName': listName, + desc, + }); + } + }, + 'click .js-add-gen-moved-trigger' (event){ + const datas = this.data(); + const desc = Utils.getTriggerActionDesc(event, this); + const boardId = Session.get('currentBoard'); + + datas.triggerVar.set({ + activityType: 'moveCard', + boardId, + 'listName':'*', + 'oldListName': '*', + desc, + }); + }, + 'click .js-add-arc-trigger' (event) { + const datas = this.data(); + const desc = Utils.getTriggerActionDesc(event, this); + const actionSelected = this.find('#arch-action').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'archived') { + datas.triggerVar.set({ + activityType: 'archivedCard', + boardId, + desc, + }); + } + if (actionSelected === 'unarchived') { + datas.triggerVar.set({ + activityType: 'restoredCard', + boardId, + desc, + }); + } + }, + + }]; + }, + +}).register('boardTriggers'); diff --git a/client/components/rules/triggers/cardTriggers.jade b/client/components/rules/triggers/cardTriggers.jade new file mode 100644 index 00000000..5226e3c4 --- /dev/null +++ b/client/components/rules/triggers/cardTriggers.jade @@ -0,0 +1,79 @@ +template(name="cardTriggers") + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-a-label-is'}} + div.trigger-dropdown + select(id="label-action") + option(value="added") {{_'r-added-to'}} + option(value="removed") {{_'r-removed-from'}} + div.trigger-text + | {{_'r-a-card'}} + div.trigger-button.js-add-gen-label-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-the-label-is'}} + div.trigger-dropdown + select(id="spec-label") + each labels + option(value="#{_id}") + = name + div.trigger-text + | {{_'r-is'}} + div.trigger-dropdown + select(id="spec-label-action") + option(value="added") {{_'r-added-to'}} + option(value="removed") {{_'r-removed-from'}} + div.trigger-text + | {{_'r-a-card'}} + div.trigger-button.js-add-spec-label-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-a-member'}} + div.trigger-dropdown + select(id="gen-member-action") + option(value="added") {{_'r-added-to'}} + option(value="removed") {{_'r-removed-from'}} + div.trigger-text + | {{_'r-a-card'}} + div.trigger-button.js-add-gen-member-trigger.js-goto-action + i.fa.fa-plus + + + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-the-member'}} + div.trigger-dropdown + input(id="spec-member",type=text,placeholder="{{_'r-name'}}") + div.trigger-text + | {{_'r-is'}} + div.trigger-dropdown + select(id="spec-member-action") + option(value="added") {{_'r-added-to'}} + option(value="removed") {{_'r-removed-from'}} + div.trigger-text + | {{_'r-a-card'}} + div.trigger-button.js-add-spec-member-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-a-attach'}} + div.trigger-text + | {{_'r-is'}} + div.trigger-dropdown + select(id="attach-action") + option(value="added") {{_'r-added-to'}} + option(value="removed") {{_'r-removed-from'}} + div.trigger-text + | {{_'r-a-card'}} + div.trigger-button.js-add-attachment-trigger.js-goto-action + i.fa.fa-plus diff --git a/client/components/rules/triggers/cardTriggers.js b/client/components/rules/triggers/cardTriggers.js new file mode 100644 index 00000000..2303a85b --- /dev/null +++ b/client/components/rules/triggers/cardTriggers.js @@ -0,0 +1,128 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.subscribe('allRules'); + }, + labels() { + const labels = Boards.findOne(Session.get('currentBoard')).labels; + for (let i = 0; i < labels.length; i++) { + if (labels[i].name === '' || labels[i].name === undefined) { + labels[i].name = labels[i].color.toUpperCase(); + } + } + return labels; + }, + events() { + return [{ + 'click .js-add-gen-label-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const datas = this.data(); + const actionSelected = this.find('#label-action').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'added') { + datas.triggerVar.set({ + activityType: 'addedLabel', + boardId, + 'labelId': '*', + desc, + }); + } + if (actionSelected === 'removed') { + datas.triggerVar.set({ + activityType: 'removedLabel', + boardId, + 'labelId': '*', + desc, + }); + } + }, + 'click .js-add-spec-label-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const datas = this.data(); + const actionSelected = this.find('#spec-label-action').value; + const labelId = this.find('#spec-label').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'added') { + datas.triggerVar.set({ + activityType: 'addedLabel', + boardId, + labelId, + desc, + }); + } + if (actionSelected === 'removed') { + datas.triggerVar.set({ + activityType: 'removedLabel', + boardId, + labelId, + desc, + }); + } + }, + 'click .js-add-gen-member-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const datas = this.data(); + const actionSelected = this.find('#gen-member-action').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'added') { + datas.triggerVar.set({ + activityType: 'joinMember', + boardId, + 'username': '*', + desc, + }); + } + if (actionSelected === 'removed') { + datas.triggerVar.set({ + activityType: 'unjoinMember', + boardId, + 'username': '*', + desc, + }); + } + }, + 'click .js-add-spec-member-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const datas = this.data(); + const actionSelected = this.find('#spec-member-action').value; + const username = this.find('#spec-member').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'added') { + datas.triggerVar.set({ + activityType: 'joinMember', + boardId, + username, + desc, + }); + } + if (actionSelected === 'removed') { + datas.triggerVar.set({ + activityType: 'unjoinMember', + boardId, + username, + desc, + }); + } + }, + 'click .js-add-attachment-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const datas = this.data(); + const actionSelected = this.find('#attach-action').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'added') { + datas.triggerVar.set({ + activityType: 'addAttachment', + boardId, + desc, + }); + } + if (actionSelected === 'removed') { + datas.triggerVar.set({ + activityType: 'deleteAttachment', + boardId, + desc, + }); + } + }, + }]; + }, +}).register('cardTriggers'); diff --git a/client/components/rules/triggers/checklistTriggers.jade b/client/components/rules/triggers/checklistTriggers.jade new file mode 100644 index 00000000..c6cd99a6 --- /dev/null +++ b/client/components/rules/triggers/checklistTriggers.jade @@ -0,0 +1,83 @@ +template(name="checklistTriggers") + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-a-checklist'}} + div.trigger-dropdown + select(id="gen-check-action") + option(value="created") {{_'r-added-to'}} + option(value="removed") {{_'r-removed-from'}} + div.trigger-text + | {{_'r-a-card'}} + div.trigger-button.js-add-gen-check-trigger.js-goto-action + i.fa.fa-plus + + + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-the-checklist'}} + div.trigger-dropdown + input(id="check-name",type=text,placeholder="{{_'r-name'}}") + div.trigger-text + | {{_'r-is'}} + div.trigger-dropdown + select(id="spec-check-action") + option(value="created") {{_'r-added-to'}} + option(value="removed") {{_'r-removed-from'}} + div.trigger-text + | {{_'r-a-card'}} + div.trigger-button.js-add-spec-check-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-a-checklist'}} + div.trigger-dropdown + select(id="gen-comp-check-action") + option(value="completed") {{_'r-completed'}} + option(value="uncompleted") {{_'r-made-incomplete'}} + div.trigger-button.js-add-gen-comp-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-the-checklist'}} + div.trigger-dropdown + input(id="spec-comp-check-name",type=text,placeholder="{{_'r-name'}}") + div.trigger-text + | {{_'r-is'}} + div.trigger-dropdown + select(id="spec-comp-check-action") + option(value="completed") {{_'r-completed'}} + option(value="uncompleted") {{_'r-made-incomplete'}} + div.trigger-button.js-add-spec-comp-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-a-item'}} + div.trigger-dropdown + select(id="check-item-gen-action") + option(value="checked") {{_'r-checked'}} + option(value="unchecked") {{_'r-unchecked'}} + div.trigger-button.js-add-gen-check-item-trigger.js-goto-action + i.fa.fa-plus + + div.trigger-item + div.trigger-content + div.trigger-text + | {{_'r-when-the-item'}} + div.trigger-dropdown + input(id="check-item-name",type=text,placeholder="{{_'r-name'}}") + div.trigger-text + | {{_'r-is'}} + div.trigger-dropdown + select(id="check-item-spec-action") + option(value="checked") {{_'r-checked'}} + option(value="unchecked") {{_'r-unchecked'}} + div.trigger-button.js-add-spec-check-item-trigger.js-goto-action + i.fa.fa-plus diff --git a/client/components/rules/triggers/checklistTriggers.js b/client/components/rules/triggers/checklistTriggers.js new file mode 100644 index 00000000..2272be29 --- /dev/null +++ b/client/components/rules/triggers/checklistTriggers.js @@ -0,0 +1,146 @@ +BlazeComponent.extendComponent({ + onCreated() { + this.subscribe('allRules'); + }, + events() { + return [{ + 'click .js-add-gen-check-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const datas = this.data(); + const actionSelected = this.find('#gen-check-action').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'created') { + datas.triggerVar.set({ + activityType: 'addChecklist', + boardId, + 'checklistName': '*', + desc, + }); + } + if (actionSelected === 'removed') { + datas.triggerVar.set({ + activityType: 'removeChecklist', + boardId, + 'checklistName': '*', + desc, + }); + } + }, + 'click .js-add-spec-check-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const datas = this.data(); + const actionSelected = this.find('#spec-check-action').value; + const checklistId = this.find('#check-name').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'created') { + datas.triggerVar.set({ + activityType: 'addChecklist', + boardId, + 'checklistName': checklistId, + desc, + }); + } + if (actionSelected === 'removed') { + datas.triggerVar.set({ + activityType: 'removeChecklist', + boardId, + 'checklistName': checklistId, + desc, + }); + } + }, + 'click .js-add-gen-comp-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + + const datas = this.data(); + const actionSelected = this.find('#gen-comp-check-action').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'completed') { + datas.triggerVar.set({ + activityType: 'completeChecklist', + boardId, + 'checklistName': '*', + desc, + }); + } + if (actionSelected === 'uncompleted') { + datas.triggerVar.set({ + activityType: 'uncompleteChecklist', + boardId, + 'checklistName': '*', + desc, + }); + } + }, + 'click .js-add-spec-comp-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const datas = this.data(); + const actionSelected = this.find('#spec-comp-check-action').value; + const checklistId = this.find('#spec-comp-check-name').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'completed') { + datas.triggerVar.set({ + activityType: 'completeChecklist', + boardId, + 'checklistName': checklistId, + desc, + }); + } + if (actionSelected === 'uncompleted') { + datas.triggerVar.set({ + activityType: 'uncompleteChecklist', + boardId, + 'checklistName': checklistId, + desc, + }); + } + }, + 'click .js-add-gen-check-item-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const datas = this.data(); + const actionSelected = this.find('#check-item-gen-action').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'checked') { + datas.triggerVar.set({ + activityType: 'checkedItem', + boardId, + 'checklistItemName': '*', + desc, + }); + } + if (actionSelected === 'unchecked') { + datas.triggerVar.set({ + activityType: 'uncheckedItem', + boardId, + 'checklistItemName': '*', + desc, + }); + } + }, + 'click .js-add-spec-check-item-trigger' (event) { + const desc = Utils.getTriggerActionDesc(event, this); + const datas = this.data(); + const actionSelected = this.find('#check-item-spec-action').value; + const checklistItemId = this.find('#check-item-name').value; + const boardId = Session.get('currentBoard'); + if (actionSelected === 'checked') { + datas.triggerVar.set({ + activityType: 'checkedItem', + boardId, + 'checklistItemName': checklistItemId, + desc, + }); + } + if (actionSelected === 'unchecked') { + datas.triggerVar.set({ + activityType: 'uncheckedItem', + boardId, + 'checklistItemName': checklistItemId, + desc, + }); + } + }, + }]; + }, + +}).register('checklistTriggers'); diff --git a/client/components/settings/connectionMethod.jade b/client/components/settings/connectionMethod.jade new file mode 100644 index 00000000..ac4c8c64 --- /dev/null +++ b/client/components/settings/connectionMethod.jade @@ -0,0 +1,6 @@ +template(name='connectionMethod') + div.at-form-authentication + label {{_ 'authentication-method'}} + select.select-authentication + each authentications + option(value="{{value}}") {{_ value}} diff --git a/client/components/settings/connectionMethod.js b/client/components/settings/connectionMethod.js new file mode 100644 index 00000000..9fe8f382 --- /dev/null +++ b/client/components/settings/connectionMethod.js @@ -0,0 +1,34 @@ +Template.connectionMethod.onCreated(function() { + this.authenticationMethods = new ReactiveVar([]); + + Meteor.call('getAuthenticationsEnabled', (_, result) => { + if (result) { + // TODO : add a management of different languages + // (ex {value: ldap, text: TAPi18n.__('ldap', {}, T9n.getLanguage() || 'en')}) + this.authenticationMethods.set([ + {value: 'password'}, + // Gets only the authentication methods availables + ...Object.entries(result).filter((e) => e[1]).map((e) => ({value: e[0]})), + ]); + } + + // If only the default authentication available, hides the select boxe + const content = $('.at-form-authentication'); + if (!(this.authenticationMethods.get().length > 1)) { + content.hide(); + } else { + content.show(); + } + }); +}); + +Template.connectionMethod.onRendered(() => { + // Moves the select boxe in the first place of the at-pwd-form div + $('.at-form-authentication').detach().prependTo('.at-pwd-form'); +}); + +Template.connectionMethod.helpers({ + authentications() { + return Template.instance().authenticationMethods.get(); + }, +}); diff --git a/client/components/settings/peopleBody.jade b/client/components/settings/peopleBody.jade index a3506a24..4d06637e 100644 --- a/client/components/settings/peopleBody.jade +++ b/client/components/settings/peopleBody.jade @@ -27,6 +27,7 @@ template(name="peopleGeneral") th {{_ 'verified'}} th {{_ 'createdAt'}} th {{_ 'active'}} + th {{_ 'authentication-method'}} th each user in peopleList +peopleRow(userId=user._id) @@ -52,6 +53,7 @@ template(name="peopleRow") | {{_ 'no'}} else | {{_ 'yes'}} + td {{_ userData.authenticationMethod }} td a.edit-user | {{_ 'edit'}} @@ -66,12 +68,18 @@ template(name="editUserPopup") | {{_ 'username'}} span.error.hide.username-taken | {{_ 'error-username-taken'}} - input.js-profile-username(type="text" value=user.username) + if isLdap + input.js-profile-username(type="text" value=user.username readonly) + else + input.js-profile-username(type="text" value=user.username) label | {{_ 'email'}} span.error.hide.email-taken | {{_ 'error-email-taken'}} - input.js-profile-email(type="email" value="{{user.emails.[0].address}}") + if isLdap + input.js-profile-email(type="email" value="{{user.emails.[0].address}}" readonly) + else + input.js-profile-email(type="email" value="{{user.emails.[0].address}}") label | {{_ 'admin'}} select.select-role.js-profile-isadmin @@ -82,9 +90,17 @@ template(name="editUserPopup") select.select-active.js-profile-isactive option(value="false") {{_ 'yes'}} option(value="true" selected="{{user.loginDisabled}}") {{_ 'no'}} + label + | {{_ 'authentication-type'}} + select.select-authenticationMethod.js-authenticationMethod + each authentications + if isSelected value + option(value="{{value}}" selected) {{_ value}} + else + option(value="{{value}}") {{_ value}} hr label | {{_ 'password'}} input.js-profile-password(type="password") - input.primary.wide(type="submit" value="{{_ 'save'}}") + input.primary.wide(type="submit" value="{{_ 'save'}}")
\ No newline at end of file diff --git a/client/components/settings/peopleBody.js b/client/components/settings/peopleBody.js index 7cc992f2..a4d70974 100644 --- a/client/components/settings/peopleBody.js +++ b/client/components/settings/peopleBody.js @@ -62,10 +62,39 @@ Template.peopleRow.helpers({ }, }); +Template.editUserPopup.onCreated(function() { + this.authenticationMethods = new ReactiveVar([]); + + Meteor.call('getAuthenticationsEnabled', (_, result) => { + if (result) { + // TODO : add a management of different languages + // (ex {value: ldap, text: TAPi18n.__('ldap', {}, T9n.getLanguage() || 'en')}) + this.authenticationMethods.set([ + {value: 'password'}, + // Gets only the authentication methods availables + ...Object.entries(result).filter((e) => e[1]).map((e) => ({value: e[0]})), + ]); + } + }); +}); + Template.editUserPopup.helpers({ user() { return Users.findOne(this.userId); }, + authentications() { + return Template.instance().authenticationMethods.get(); + }, + isSelected(match) { + const userId = Template.instance().data.userId; + const selected = Users.findOne(userId).authenticationMethod; + return selected === match; + }, + isLdap() { + const userId = Template.instance().data.userId; + const selected = Users.findOne(userId).authenticationMethod; + return selected === 'ldap'; + }, }); BlazeComponent.extendComponent({ @@ -91,6 +120,7 @@ Template.editUserPopup.events({ const isAdmin = tpl.find('.js-profile-isadmin').value.trim(); const isActive = tpl.find('.js-profile-isactive').value.trim(); const email = tpl.find('.js-profile-email').value.trim(); + const authentication = tpl.find('.js-authenticationMethod').value.trim(); const isChangePassword = password.length > 0; const isChangeUserName = username !== user.username; @@ -101,6 +131,7 @@ Template.editUserPopup.events({ 'profile.fullname': fullname, 'isAdmin': isAdmin === 'true', 'loginDisabled': isActive === 'true', + 'authenticationMethod': authentication, }, }); diff --git a/client/lib/modal.js b/client/lib/modal.js index d5350264..3c27a179 100644 --- a/client/lib/modal.js +++ b/client/lib/modal.js @@ -4,6 +4,7 @@ window.Modal = new class { constructor() { this._currentModal = new ReactiveVar(closedValue); this._onCloseGoTo = ''; + this._isWideModal = false; } getHeaderName() { @@ -20,6 +21,10 @@ window.Modal = new class { return this.getTemplateName() !== closedValue; } + isWide(){ + return this._isWideModal; + } + close() { this._currentModal.set(closedValue); if (this._onCloseGoTo) { @@ -27,9 +32,16 @@ window.Modal = new class { } } + openWide(modalName, { header = '', onCloseGoTo = ''} = {}) { + this._currentModal.set({ header, modalName }); + this._onCloseGoTo = onCloseGoTo; + this._isWideModal = true; + } + open(modalName, { header = '', onCloseGoTo = ''} = {}) { this._currentModal.set({ header, modalName }); this._onCloseGoTo = onCloseGoTo; + } }(); @@ -38,5 +50,5 @@ Blaze.registerHelper('Modal', Modal); EscapeActions.register('modalWindow', () => Modal.close(), () => Modal.isOpen(), - { noClickEscapeOn: '.modal-content' } + { noClickEscapeOn: '.modal-container' } ); diff --git a/client/lib/utils.js b/client/lib/utils.js index 5349e500..525cfb83 100644 --- a/client/lib/utils.js +++ b/client/lib/utils.js @@ -39,11 +39,11 @@ Utils = { if (!prevData && !nextData) { base = 0; increment = 1; - // If we drop the card in the first position + // If we drop the card in the first position } else if (!prevData) { base = nextData.sort - 1; increment = -1; - // If we drop the card in the last position + // If we drop the card in the last position } else if (!nextData) { base = prevData.sort + 1; increment = 1; @@ -71,11 +71,11 @@ Utils = { if (!prevCardDomElement && !nextCardDomElement) { base = 0; increment = 1; - // If we drop the card in the first position + // If we drop the card in the first position } else if (!prevCardDomElement) { base = Blaze.getData(nextCardDomElement).sort - 1; increment = -1; - // If we drop the card in the last position + // If we drop the card in the last position } else if (!nextCardDomElement) { base = Blaze.getData(prevCardDomElement).sort + 1; increment = 1; @@ -189,6 +189,27 @@ Utils = { window._paq.push(['trackPageView']); } }, + + getTriggerActionDesc(event, tempInstance) { + const jqueryEl = tempInstance.$(event.currentTarget.parentNode); + const triggerEls = jqueryEl.find('.trigger-content').children(); + let finalString = ''; + for (let i = 0; i < triggerEls.length; i++) { + const element = tempInstance.$(triggerEls[i]); + if (element.hasClass('trigger-text')) { + finalString += element.text().toLowerCase(); + } else if (element.find('select').length > 0) { + finalString += element.find('select option:selected').text().toLowerCase(); + } else if (element.find('input').length > 0) { + finalString += element.find('input').val(); + } + // Add space + if (i !== length - 1) { + finalString += ' '; + } + } + return finalString; + }, }; // A simple tracker dependency that we invalidate every time the window is diff --git a/docker-compose.yml b/docker-compose.yml index 7509bbc9..a2228dac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,11 @@ version: '2' +# Using prebuilt image: docker-compose up -d --no-build + services: wekandb: - image: mongo:3.2.21 + image: mongo:4.0.3 container_name: wekan-db restart: always command: mongod --smallfiles --oplogSize 128 @@ -16,7 +18,7 @@ services: - wekan-db-dump:/dump wekan: - image: quay.io/wekan/wekan + image: quay.io/wekan/wekan:edge container_name: wekan-app restart: always networks: @@ -63,6 +65,9 @@ services: # What to send to Outgoing Webhook, or leave out. Example, that includes all that are default: cardId,listId,oldListId,boardId,comment,user,card,commentId . # example: WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId - WEBHOOKS_ATTRIBUTES='' + # Enable the OAuth2 connection + # example: OAUTH2_ENABLED=true + - OAUTH2_ENABLED=false # OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2 # OAuth2 Client ID, for example from Rocket.Chat. Example: abcde12345 # example: OAUTH2_CLIENT_ID=abcde12345 @@ -82,6 +87,124 @@ services: # OAuth2 Token Endpoint. Example: /oauth/token # example: OAUTH2_TOKEN_ENDPOINT=/oauth/token - OAUTH2_TOKEN_ENDPOINT='' + # LDAP_ENABLE : Enable or not the connection by the LDAP + # example : LDAP_ENABLE=true + - LDAP_ENABLE=false + # LDAP_PORT : The port of the LDAP server + # example : LDAP_PORT=389 + - LDAP_PORT=389 + # LDAP_HOST : The host server for the LDAP server + # example : LDAP_HOST=localhost + - LDAP_HOST='' + # LDAP_BASEDN : The base DN for the LDAP Tree + # example : LDAP_BASEDN=ou=user,dc=example,dc=org + - LDAP_BASEDN='' + # LDAP_LOGIN_FALLBACK : Fallback on the default authentication method + # example : LDAP_LOGIN_FALLBACK=true + - LDAP_LOGIN_FALLBACK=false + # LDAP_RECONNECT : Reconnect to the server if the connection is lost + # example : LDAP_RECONNECT=false + - LDAP_RECONNECT=true + # LDAP_TIMEOUT : Overall timeout, in milliseconds + # example : LDAP_TIMEOUT=12345 + - LDAP_TIMEOUT=10000 + # LDAP_IDLE_TIMEOUT : Specifies the timeout for idle LDAP connections in milliseconds + # example : LDAP_IDLE_TIMEOUT=12345 + - LDAP_IDLE_TIMEOUT=10000 + # LDAP_CONNECT_TIMEOUT : Connection timeout, in milliseconds + # example : LDAP_CONNECT_TIMEOUT=12345 + - LDAP_CONNECT_TIMEOUT=10000 + # LDAP_AUTHENTIFICATION : If the LDAP needs a user account to search + # example : LDAP_AUTHENTIFICATION=true + - LDAP_AUTHENTIFICATION=false + # LDAP_AUTHENTIFICATION_USERDN : The search user DN + # example : LDAP_AUTHENTIFICATION_USERDN=cn=admin,dc=example,dc=org + - LDAP_AUTHENTIFICATION_USERDN='' + # LDAP_AUTHENTIFICATION_PASSWORD : The password for the search user + # example : AUTHENTIFICATION_PASSWORD=admin + - LDAP_AUTHENTIFICATION_PASSWORD='' + # LDAP_LOG_ENABLED : Enable logs for the module + # example : LDAP_LOG_ENABLED=true + - LDAP_LOG_ENABLED=false + # LDAP_BACKGROUND_SYNC : If the sync of the users should be done in the background + # example : LDAP_BACKGROUND_SYNC=true + - LDAP_BACKGROUND_SYNC=false + # LDAP_BACKGROUND_SYNC_INTERVAL : At which interval does the background task sync in milliseconds + # example : LDAP_BACKGROUND_SYNC_INTERVAL=12345 + - LDAP_BACKGROUND_SYNC_INTERVAL=100 + # LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED : + # example : LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=true + - LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false + # LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS : + # example : LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=true + - LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false + # LDAP_ENCRYPTION : If using LDAPS + # example : LDAP_ENCRYPTION=true + - LDAP_ENCRYPTION=false + # LDAP_CA_CERT : The certification for the LDAPS server + # example : LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+zCCA+OgAwIBAgIkAhwR/6TVLmdRY6hHxvUFWc0+Enmu/Hu6cj+G2FIdAgIC...-----END CERTIFICATE----- + - LDAP_CA_CERT='' + # LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate + # example : LDAP_REJECT_UNAUTHORIZED=true + - LDAP_REJECT_UNAUTHORIZED=false + # LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed + # example : LDAP_USER_SEARCH_FILTER= + - LDAP_USER_SEARCH_FILTER='' + # LDAP_USER_SEARCH_SCOPE : Base (search only in the provided DN), one (search only in the provided DN and one level deep), or subtree (search the whole subtree) + # example : LDAP_USER_SEARCH_SCOPE=one + - LDAP_USER_SEARCH_SCOPE='' + # LDAP_USER_SEARCH_FIELD : Which field is used to find the user + # example : LDAP_USER_SEARCH_FIELD=uid + - LDAP_USER_SEARCH_FIELD='' + # LDAP_SEARCH_PAGE_SIZE : Used for pagination (0=unlimited) + # example : LDAP_SEARCH_PAGE_SIZE=12345 + - LDAP_SEARCH_PAGE_SIZE=0 + # LDAP_SEARCH_SIZE_LIMIT : The limit number of entries (0=unlimited) + # example : LDAP_SEARCH_SIZE_LIMIT=12345 + - LDAP_SEARCH_SIZE_LIMIT=0 + # LDAP_GROUP_FILTER_ENABLE : Enable group filtering + # example : LDAP_GROUP_FILTER_ENABLE=true + - LDAP_GROUP_FILTER_ENABLE=false + # LDAP_GROUP_FILTER_OBJECTCLASS : The object class for filtering + # example : LDAP_GROUP_FILTER_OBJECTCLASS=group + - LDAP_GROUP_FILTER_OBJECTCLASS='' + # LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE : + # example : + - LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE='' + # LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE : + # example : + - LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE='' + # LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT : + # example : + - LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT='' + # LDAP_GROUP_FILTER_GROUP_NAME : + # example : + - LDAP_GROUP_FILTER_GROUP_NAME='' + # LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier) + # example : LDAP_UNIQUE_IDENTIFIER_FIELD=guid + - LDAP_UNIQUE_IDENTIFIER_FIELD='' + # LDAP_UTF8_NAMES_SLUGIFY : Convert the username to utf8 + # example : LDAP_UTF8_NAMES_SLUGIFY=false + - LDAP_UTF8_NAMES_SLUGIFY=true + # LDAP_USERNAME_FIELD : Which field contains the ldap username + # example : LDAP_USERNAME_FIELD=username + - LDAP_USERNAME_FIELD='' + # LDAP_MERGE_EXISTING_USERS : + # example : LDAP_MERGE_EXISTING_USERS=true + - LDAP_MERGE_EXISTING_USERS=false + # LDAP_SYNC_USER_DATA : + # example : LDAP_SYNC_USER_DATA=true + - LDAP_SYNC_USER_DATA=false + # LDAP_SYNC_USER_DATA_FIELDMAP : + # example : LDAP_SYNC_USER_DATA_FIELDMAP={\"cn\":\"name\", \"mail\":\"email\"} + - LDAP_SYNC_USER_DATA_FIELDMAP='' + # LDAP_SYNC_GROUP_ROLES : + # example : + - LDAP_SYNC_GROUP_ROLES='' + # LDAP_DEFAULT_DOMAIN : The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP + # example : + - LDAP_DEFAULT_DOMAIN='' + depends_on: - wekandb diff --git a/models/actions.js b/models/actions.js new file mode 100644 index 00000000..0430b044 --- /dev/null +++ b/models/actions.js @@ -0,0 +1,19 @@ +Actions = new Mongo.Collection('actions'); + +Actions.allow({ + insert(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + update(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + remove(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, +}); + +Actions.helpers({ + description() { + return this.desc; + }, +}); diff --git a/models/activities.js b/models/activities.js index e49cbf0a..47e3ff1e 100644 --- a/models/activities.js +++ b/models/activities.js @@ -56,6 +56,14 @@ Activities.before.insert((userId, doc) => { doc.createdAt = new Date(); }); + +Activities.after.insert((userId, doc) => { + const activity = Activities._transform(doc); + RulesHelper.executeRules(activity); + +}); + + if (Meteor.isServer) { // For efficiency create indexes on the date of creation, and on the date of // creation in conjunction with the card or board id, as corresponding views diff --git a/models/attachments.js b/models/attachments.js index 91dd0dbc..3da067de 100644 --- a/models/attachments.js +++ b/models/attachments.js @@ -86,5 +86,12 @@ if (Meteor.isServer) { Activities.remove({ attachmentId: doc._id, }); + Activities.insert({ + userId, + type: 'card', + activityType: 'deleteAttachment', + boardId: doc.boardId, + cardId: doc.cardId, + }); }); } diff --git a/models/boards.js b/models/boards.js index 2a21d6da..52d0ca87 100644 --- a/models/boards.js +++ b/models/boards.js @@ -280,6 +280,10 @@ Boards.helpers({ return _.findWhere(this.labels, { name, color }); }, + getLabelById(labelId){ + return _.findWhere(this.labels, { _id: labelId }); + }, + labelIndex(labelId) { return _.pluck(this.labels, '_id').indexOf(labelId); }, @@ -537,11 +541,10 @@ Boards.mutations({ }; }, - setMemberPermission(memberId, isAdmin, isNoComments, isCommentOnly) { + setMemberPermission(memberId, isAdmin, isNoComments, isCommentOnly, currentUserId = Meteor.userId()) { const memberIndex = this.memberIndex(memberId); - // do not allow change permission of self - if (memberId === Meteor.userId()) { + if (memberId === currentUserId) { isAdmin = this.members[memberIndex].isAdmin; } @@ -923,4 +926,29 @@ if (Meteor.isServer) { }); } }); + + JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', function (req, res) { + try { + const boardId = req.params.boardId; + const memberId = req.params.memberId; + const {isAdmin, isNoComments, isCommentOnly} = req.body; + Authentication.checkBoardAccess(req.userId, boardId); + const board = Boards.findOne({ _id: boardId }); + function isTrue(data){ + return data.toLowerCase() === 'true'; + } + board.setMemberPermission(memberId, isTrue(isAdmin), isTrue(isNoComments), isTrue(isCommentOnly), req.userId); + + JsonRoutes.sendResult(res, { + code: 200, + data: query, + }); + } + catch (error) { + JsonRoutes.sendResult(res, { + code: 200, + data: error, + }); + } + }); } diff --git a/models/cards.js b/models/cards.js index 7a0bcd5c..25692c25 100644 --- a/models/cards.js +++ b/models/cards.js @@ -276,14 +276,22 @@ Cards.helpers({ return Cards.find({ parentId: this._id, archived: false, - }, {sort: { sort: 1 } }); + }, { + sort: { + sort: 1, + }, + }); }, allSubtasks() { return Cards.find({ parentId: this._id, archived: false, - }, {sort: { sort: 1 } }); + }, { + sort: { + sort: 1, + }, + }); }, subtasksCount() { @@ -296,7 +304,8 @@ Cards.helpers({ subtasksFinishedCount() { return Cards.find({ parentId: this._id, - archived: true}).count(); + archived: true, + }).count(); }, subtasksFinished() { @@ -328,12 +337,9 @@ Cards.helpers({ }); //search for "True Value" which is for DropDowns other then the Value (which is the id) let trueValue = customField.value; - if (definition.settings.dropdownItems && definition.settings.dropdownItems.length > 0) - { - for (let i = 0; i < definition.settings.dropdownItems.length; i++) - { - if (definition.settings.dropdownItems[i]._id === customField.value) - { + if (definition.settings.dropdownItems && definition.settings.dropdownItems.length > 0) { + for (let i = 0; i < definition.settings.dropdownItems.length; i++) { + if (definition.settings.dropdownItems[i]._id === customField.value) { trueValue = definition.settings.dropdownItems[i].name; } } @@ -358,8 +364,10 @@ Cards.helpers({ }, canBeRestored() { - const list = Lists.findOne({_id: this.listId}); - if(!list.getWipLimit('soft') && list.getWipLimit('enabled') && list.getWipLimit('value') === list.cards().count()){ + const list = Lists.findOne({ + _id: this.listId, + }); + if (!list.getWipLimit('soft') && list.getWipLimit('enabled') && list.getWipLimit('value') === list.cards().count()) { return false; } return true; @@ -424,7 +432,7 @@ Cards.helpers({ }, parentString(sep) { - return this.parentList().map(function(elem){ + return this.parentList().map(function(elem) { return elem.title; }).join(sep); }, @@ -826,19 +834,65 @@ Cards.helpers({ Cards.mutations({ applyToChildren(funct) { - Cards.find({ parentId: this._id }).forEach((card) => { + Cards.find({ + parentId: this._id, + }).forEach((card) => { funct(card); }); }, archive() { - this.applyToChildren((card) => { return card.archive(); }); - return {$set: {archived: true}}; + this.applyToChildren((card) => { + return card.archive(); + }); + return { + $set: { + archived: true, + }, + }; }, restore() { - this.applyToChildren((card) => { return card.restore(); }); - return {$set: {archived: false}}; + this.applyToChildren((card) => { + return card.restore(); + }); + return { + $set: { + archived: false, + }, + }; + }, + + setTitle(title) { + return { + $set: { + title, + }, + }; + }, + + setDescription(description) { + return { + $set: { + description, + }, + }; + }, + + setRequestedBy(requestedBy) { + return { + $set: { + requestedBy, + }, + }; + }, + + setAssignedBy(assignedBy) { + return { + $set: { + assignedBy, + }, + }; }, move(swimlaneId, listId, sortIndex) { @@ -850,15 +904,25 @@ Cards.mutations({ sort: sortIndex, }; - return {$set: mutatedFields}; + return { + $set: mutatedFields, + }; }, addLabel(labelId) { - return {$addToSet: {labelIds: labelId}}; + return { + $addToSet: { + labelIds: labelId, + }, + }; }, removeLabel(labelId) { - return {$pull: {labelIds: labelId}}; + return { + $pull: { + labelIds: labelId, + }, + }; }, toggleLabel(labelId) { @@ -869,12 +933,49 @@ Cards.mutations({ } }, + assignMember(memberId) { + return { + $addToSet: { + members: memberId, + }, + }; + }, + + unassignMember(memberId) { + return { + $pull: { + members: memberId, + }, + }; + }, + + toggleMember(memberId) { + if (this.members && this.members.indexOf(memberId) > -1) { + return this.unassignMember(memberId); + } else { + return this.assignMember(memberId); + } + }, + assignCustomField(customFieldId) { - return {$addToSet: {customFields: {_id: customFieldId, value: null}}}; + return { + $addToSet: { + customFields: { + _id: customFieldId, + value: null, + }, + }, + }; }, unassignCustomField(customFieldId) { - return {$pull: {customFields: {_id: customFieldId}}}; + return { + $pull: { + customFields: { + _id: customFieldId, + }, + }, + }; }, toggleCustomField(customFieldId) { @@ -889,7 +990,9 @@ Cards.mutations({ // todo const index = this.customFieldIndex(customFieldId); if (index > -1) { - const update = {$set: {}}; + const update = { + $set: {}, + }; update.$set[`customFields.${index}.value`] = value; return update; } @@ -899,19 +1002,119 @@ Cards.mutations({ }, setCover(coverId) { - return {$set: {coverId}}; + return { + $set: { + coverId, + }, + }; }, unsetCover() { - return {$unset: {coverId: ''}}; + return { + $unset: { + coverId: '', + }, + }; + }, + + setReceived(receivedAt) { + return { + $set: { + receivedAt, + }, + }; + }, + + unsetReceived() { + return { + $unset: { + receivedAt: '', + }, + }; + }, + + setStart(startAt) { + return { + $set: { + startAt, + }, + }; + }, + + unsetStart() { + return { + $unset: { + startAt: '', + }, + }; + }, + + setDue(dueAt) { + return { + $set: { + dueAt, + }, + }; + }, + + unsetDue() { + return { + $unset: { + dueAt: '', + }, + }; + }, + + setEnd(endAt) { + return { + $set: { + endAt, + }, + }; + }, + + unsetEnd() { + return { + $unset: { + endAt: '', + }, + }; + }, + + setOvertime(isOvertime) { + return { + $set: { + isOvertime, + }, + }; + }, + + setSpentTime(spentTime) { + return { + $set: { + spentTime, + }, + }; + }, + + unsetSpentTime() { + return { + $unset: { + spentTime: '', + isOvertime: false, + }, + }; }, setParentId(parentId) { - return {$set: {parentId}}; + return { + $set: { + parentId, + }, + }; }, }); - //FUNCTIONS FOR creation of Activities function cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId) { @@ -921,6 +1124,7 @@ function cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId) { userId, oldListId, activityType: 'moveCard', + listName: Lists.findOne(doc.listId).title, listId: doc.listId, boardId: doc.boardId, cardId: doc._id, @@ -936,6 +1140,7 @@ function cardState(userId, doc, fieldNames) { Activities.insert({ userId, activityType: 'archivedCard', + listName: Lists.findOne(doc.listId).title, boardId: doc.boardId, listId: doc.listId, cardId: doc._id, @@ -945,6 +1150,7 @@ function cardState(userId, doc, fieldNames) { userId, activityType: 'restoredCard', boardId: doc.boardId, + listName: Lists.findOne(doc.listId).title, listId: doc.listId, cardId: doc._id, }); @@ -959,10 +1165,11 @@ function cardMembers(userId, doc, fieldNames, modifier) { // Say hello to the new member if (modifier.$addToSet && modifier.$addToSet.members) { memberId = modifier.$addToSet.members; + const username = Users.findOne(memberId).username; if (!_.contains(doc.members, memberId)) { Activities.insert({ userId, - memberId, + username, activityType: 'joinMember', boardId: doc.boardId, cardId: doc._id, @@ -973,11 +1180,12 @@ function cardMembers(userId, doc, fieldNames, modifier) { // Say goodbye to the former member if (modifier.$pull && modifier.$pull.members) { memberId = modifier.$pull.members; + const username = Users.findOne(memberId).username; // Check that the former member is member of the card if (_.contains(doc.members, memberId)) { Activities.insert({ userId, - memberId, + username, activityType: 'unjoinMember', boardId: doc.boardId, cardId: doc._id, @@ -986,11 +1194,47 @@ function cardMembers(userId, doc, fieldNames, modifier) { } } +function cardLabels(userId, doc, fieldNames, modifier) { + if (!_.contains(fieldNames, 'labelIds')) + return; + let labelId; + // Say hello to the new label + if (modifier.$addToSet && modifier.$addToSet.labelIds) { + labelId = modifier.$addToSet.labelIds; + if (!_.contains(doc.labelIds, labelId)) { + const act = { + userId, + labelId, + activityType: 'addedLabel', + boardId: doc.boardId, + cardId: doc._id, + }; + Activities.insert(act); + } + } + + // Say goodbye to the label + if (modifier.$pull && modifier.$pull.labelIds) { + labelId = modifier.$pull.labelIds; + // Check that the former member is member of the card + if (_.contains(doc.labelIds, labelId)) { + Activities.insert({ + userId, + labelId, + activityType: 'removedLabel', + boardId: doc.boardId, + cardId: doc._id, + }); + } + } +} + function cardCreation(userId, doc) { Activities.insert({ userId, activityType: 'createCard', boardId: doc.boardId, + listName: Lists.findOne(doc.listId).title, listId: doc.listId, cardId: doc._id, swimlaneId: doc.swimlaneId, @@ -1015,7 +1259,6 @@ function cardRemover(userId, doc) { }); } - if (Meteor.isServer) { // Cards are often fetched within a board, so we create an index to make these // queries more efficient. @@ -1039,7 +1282,7 @@ if (Meteor.isServer) { }); //New activity for card moves - Cards.after.update(function (userId, doc, fieldNames) { + Cards.after.update(function(userId, doc, fieldNames) { const oldListId = this.previous.listId; const oldSwimlaneId = this.previous.swimlaneId; cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId); @@ -1050,6 +1293,11 @@ if (Meteor.isServer) { cardMembers(userId, doc, fieldNames, modifier); }); + // Add a new activity if we add or remove a label to the card + Cards.before.update((userId, doc, fieldNames, modifier) => { + cardLabels(userId, doc, fieldNames, modifier); + }); + // Remove all activities associated with a card if we remove the card // Remove also card_comments / checklists / attachments Cards.after.remove((userId, doc) => { @@ -1081,13 +1329,17 @@ if (Meteor.isServer) { } //LISTS REST API if (Meteor.isServer) { - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function (req, res) { + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function(req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; Authentication.checkBoardAccess(req.userId, paramBoardId); JsonRoutes.sendResult(res, { code: 200, - data: Cards.find({boardId: paramBoardId, listId: paramListId, archived: false}).map(function (doc) { + data: Cards.find({ + boardId: paramBoardId, + listId: paramListId, + archived: false, + }).map(function(doc) { return { _id: doc._id, title: doc.title, @@ -1097,24 +1349,31 @@ if (Meteor.isServer) { }); }); - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) { + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; const paramCardId = req.params.cardId; Authentication.checkBoardAccess(req.userId, paramBoardId); JsonRoutes.sendResult(res, { code: 200, - data: Cards.findOne({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}), + data: Cards.findOne({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false, + }), }); }); - JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function (req, res) { + JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramListId = req.params.listId; - const check = Users.findOne({_id: req.body.authorId}); + const check = Users.findOne({ + _id: req.body.authorId, + }); const members = req.body.members || [req.body.authorId]; - if (typeof check !== 'undefined') { + if (typeof check !== 'undefined') { const id = Cards.direct.insert({ title: req.body.title, boardId: paramBoardId, @@ -1132,7 +1391,9 @@ if (Meteor.isServer) { }, }); - const card = Cards.findOne({_id:id}); + const card = Cards.findOne({ + _id: id, + }); cardCreation(req.body.authorId, card); } else { @@ -1142,7 +1403,7 @@ if (Meteor.isServer) { } }); - JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) { + JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; @@ -1150,27 +1411,63 @@ if (Meteor.isServer) { if (req.body.hasOwnProperty('title')) { const newTitle = req.body.title; - Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, - {$set: {title: newTitle}}); + Cards.direct.update({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false, + }, { + $set: { + title: newTitle, + }, + }); } if (req.body.hasOwnProperty('listId')) { const newParamListId = req.body.listId; - Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, - {$set: {listId: newParamListId}}); + Cards.direct.update({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false, + }, { + $set: { + listId: newParamListId, + }, + }); - const card = Cards.findOne({_id: paramCardId} ); - cardMove(req.body.authorId, card, {fieldName: 'listId'}, paramListId); + const card = Cards.findOne({ + _id: paramCardId, + }); + cardMove(req.body.authorId, card, { + fieldName: 'listId', + }, paramListId); } if (req.body.hasOwnProperty('description')) { const newDescription = req.body.description; - Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, - {$set: {description: newDescription}}); + Cards.direct.update({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false, + }, { + $set: { + description: newDescription, + }, + }); } if (req.body.hasOwnProperty('labelIds')) { const newlabelIds = req.body.labelIds; - Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}, - {$set: {labelIds: newlabelIds}}); + Cards.direct.update({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false, + }, { + $set: { + labelIds: newlabelIds, + }, + }); } if (req.body.hasOwnProperty('requestedBy')) { const newrequestedBy = req.body.requestedBy; @@ -1225,15 +1522,20 @@ if (Meteor.isServer) { }); }); - - JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) { + JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) { Authentication.checkUserId(req.userId); const paramBoardId = req.params.boardId; const paramListId = req.params.listId; const paramCardId = req.params.cardId; - Cards.direct.remove({_id: paramCardId, listId: paramListId, boardId: paramBoardId}); - const card = Cards.find({_id: paramCardId} ); + Cards.direct.remove({ + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + }); + const card = Cards.find({ + _id: paramCardId, + }); cardRemover(req.body.authorId, card); JsonRoutes.sendResult(res, { code: 200, diff --git a/models/checklistItems.js b/models/checklistItems.js index e075eda2..c85c0260 100644 --- a/models/checklistItems.js +++ b/models/checklistItems.js @@ -44,6 +44,12 @@ ChecklistItems.mutations({ setTitle(title) { return { $set: { title } }; }, + check(){ + return { $set: { isFinished: true } }; + }, + uncheck(){ + return { $set: { isFinished: false } }; + }, toggleItem() { return { $set: { isFinished: !this.isFinished } }; }, @@ -70,21 +76,100 @@ function itemCreation(userId, doc) { boardId, checklistId: doc.checklistId, checklistItemId: doc._id, + checklistItemName:doc.title, }); } function itemRemover(userId, doc) { + const card = Cards.findOne(doc.cardId); + const boardId = card.boardId; + Activities.insert({ + userId, + activityType: 'removedChecklistItem', + cardId: doc.cardId, + boardId, + checklistId: doc.checklistId, + checklistItemId: doc._id, + checklistItemName:doc.title, + }); Activities.remove({ checklistItemId: doc._id, }); } +function publishCheckActivity(userId, doc){ + const card = Cards.findOne(doc.cardId); + const boardId = card.boardId; + let activityType; + if(doc.isFinished){ + activityType = 'checkedItem'; + }else{ + activityType = 'uncheckedItem'; + } + const act = { + userId, + activityType, + cardId: doc.cardId, + boardId, + checklistId: doc.checklistId, + checklistItemId: doc._id, + checklistItemName:doc.title, + }; + Activities.insert(act); +} + +function publishChekListCompleted(userId, doc){ + const card = Cards.findOne(doc.cardId); + const boardId = card.boardId; + const checklistId = doc.checklistId; + const checkList = Checklists.findOne({_id:checklistId}); + if(checkList.isFinished()){ + const act = { + userId, + activityType: 'checklistCompleted', + cardId: doc.cardId, + boardId, + checklistId: doc.checklistId, + checklistName: checkList.title, + }; + Activities.insert(act); + } +} + +function publishChekListUncompleted(userId, doc){ + const card = Cards.findOne(doc.cardId); + const boardId = card.boardId; + const checklistId = doc.checklistId; + const checkList = Checklists.findOne({_id:checklistId}); + if(checkList.isFinished()){ + const act = { + userId, + activityType: 'checklistUncompleted', + cardId: doc.cardId, + boardId, + checklistId: doc.checklistId, + checklistName: checkList.title, + }; + Activities.insert(act); + } +} + // Activities if (Meteor.isServer) { Meteor.startup(() => { ChecklistItems._collection._ensureIndex({ checklistId: 1 }); }); + ChecklistItems.after.update((userId, doc, fieldNames) => { + publishCheckActivity(userId, doc); + publishChekListCompleted(userId, doc, fieldNames); + }); + + ChecklistItems.before.update((userId, doc, fieldNames) => { + publishChekListUncompleted(userId, doc, fieldNames); + }); + + ChecklistItems.after.insert((userId, doc) => { itemCreation(userId, doc); }); diff --git a/models/checklists.js b/models/checklists.js index c58453ef..425a10b2 100644 --- a/models/checklists.js +++ b/models/checklists.js @@ -47,6 +47,18 @@ Checklists.helpers({ isFinished() { return 0 !== this.itemCount() && this.itemCount() === this.finishedCount(); }, + checkAllItems(){ + const checkItems = ChecklistItems.find({checklistId: this._id}); + checkItems.forEach(function(item){ + item.check(); + }); + }, + uncheckAllItems(){ + const checkItems = ChecklistItems.find({checklistId: this._id}); + checkItems.forEach(function(item){ + item.uncheck(); + }); + }, itemIndex(itemId) { const items = self.findOne({_id : this._id}).items; return _.pluck(items, '_id').indexOf(itemId); @@ -91,6 +103,7 @@ if (Meteor.isServer) { cardId: doc.cardId, boardId: Cards.findOne(doc.cardId).boardId, checklistId: doc._id, + checklistName:doc.title, }); }); @@ -101,6 +114,16 @@ if (Meteor.isServer) { Activities.remove(activity._id); }); } + Activities.insert({ + userId, + activityType: 'removeChecklist', + cardId: doc.cardId, + boardId: Cards.findOne(doc.cardId).boardId, + checklistId: doc._id, + checklistName:doc.title, + }); + + }); } diff --git a/models/export.js b/models/export.js index 6c0b43fd..0911a631 100644 --- a/models/export.js +++ b/models/export.js @@ -14,7 +14,7 @@ if (Meteor.isServer) { * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/ * for detailed explanations */ - JsonRoutes.add('get', '/api/boards/:boardId/export', function (req, res) { + JsonRoutes.add('get', '/api/boards/:boardId/export', function(req, res) { const boardId = req.params.boardId; let user = null; // todo XXX for real API, first look for token in Authentication: header @@ -28,8 +28,11 @@ if (Meteor.isServer) { } const exporter = new Exporter(boardId); - if(exporter.canExport(user)) { - JsonRoutes.sendResult(res, { code: 200, data: exporter.build() }); + if (exporter.canExport(user)) { + JsonRoutes.sendResult(res, { + code: 200, + data: exporter.build(), + }); } else { // we could send an explicit error message, but on the other hand the only // way to get there is by hacking the UI so let's keep it raw. @@ -47,24 +50,49 @@ class Exporter { const byBoard = { boardId: this._boardId }; const byBoardNoLinked = { boardId: this._boardId, linkedId: '' }; // we do not want to retrieve boardId in related elements - const noBoardId = { fields: { boardId: 0 } }; + const noBoardId = { + fields: { + boardId: 0, + }, + }; const result = { _format: 'wekan-board-1.0.0', }; - _.extend(result, Boards.findOne(this._boardId, { fields: { stars: 0 } })); + _.extend(result, Boards.findOne(this._boardId, { + fields: { + stars: 0, + }, + })); result.lists = Lists.find(byBoard, noBoardId).fetch(); result.cards = Cards.find(byBoardNoLinked, noBoardId).fetch(); result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch(); result.customFields = CustomFields.find(byBoard, noBoardId).fetch(); result.comments = CardComments.find(byBoard, noBoardId).fetch(); result.activities = Activities.find(byBoard, noBoardId).fetch(); + result.rules = Rules.find(byBoard, noBoardId).fetch(); result.checklists = []; result.checklistItems = []; result.subtaskItems = []; + result.triggers = []; + result.actions = []; result.cards.forEach((card) => { - result.checklists.push(...Checklists.find({ cardId: card._id }).fetch()); - result.checklistItems.push(...ChecklistItems.find({ cardId: card._id }).fetch()); - result.subtaskItems.push(...Cards.find({ parentid: card._id }).fetch()); + result.checklists.push(...Checklists.find({ + cardId: card._id, + }).fetch()); + result.checklistItems.push(...ChecklistItems.find({ + cardId: card._id, + }).fetch()); + result.subtaskItems.push(...Cards.find({ + parentid: card._id, + }).fetch()); + }); + result.rules.forEach((rule) => { + result.triggers.push(...Triggers.find({ + _id: rule.triggerId, + }, noBoardId).fetch()); + result.actions.push(...Actions.find({ + _id: rule.actionId, + }, noBoardId).fetch()); }); // [Old] for attachments we only export IDs and absolute url to original doc @@ -101,18 +129,34 @@ class Exporter { // 1- only exports users that are linked somehow to that board // 2- do not export any sensitive information const users = {}; - result.members.forEach((member) => { users[member.userId] = true; }); - result.lists.forEach((list) => { users[list.userId] = true; }); + result.members.forEach((member) => { + users[member.userId] = true; + }); + result.lists.forEach((list) => { + users[list.userId] = true; + }); result.cards.forEach((card) => { users[card.userId] = true; if (card.members) { - card.members.forEach((memberId) => { users[memberId] = true; }); + card.members.forEach((memberId) => { + users[memberId] = true; + }); } }); - result.comments.forEach((comment) => { users[comment.userId] = true; }); - result.activities.forEach((activity) => { users[activity.userId] = true; }); - result.checklists.forEach((checklist) => { users[checklist.userId] = true; }); - const byUserIds = { _id: { $in: Object.getOwnPropertyNames(users) } }; + result.comments.forEach((comment) => { + users[comment.userId] = true; + }); + result.activities.forEach((activity) => { + users[activity.userId] = true; + }); + result.checklists.forEach((checklist) => { + users[checklist.userId] = true; + }); + const byUserIds = { + _id: { + $in: Object.getOwnPropertyNames(users), + }, + }; // we use whitelist to be sure we do not expose inadvertently // some secret fields that gets added to User later. const userFields = { diff --git a/models/lists.js b/models/lists.js index 9bcb9ba1..b99fe8f5 100644 --- a/models/lists.js +++ b/models/lists.js @@ -86,6 +86,17 @@ Lists.helpers({ { sort: ['sort'] }); }, + cardsUnfiltered(swimlaneId) { + const selector = { + listId: this._id, + archived: false, + }; + if (swimlaneId) + selector.swimlaneId = swimlaneId; + return Cards.find(selector, + { sort: ['sort'] }); + }, + allCards() { return Cards.find({ listId: this._id }); }, diff --git a/models/rules.js b/models/rules.js new file mode 100644 index 00000000..7d971980 --- /dev/null +++ b/models/rules.js @@ -0,0 +1,48 @@ +Rules = new Mongo.Collection('rules'); + +Rules.attachSchema(new SimpleSchema({ + title: { + type: String, + optional: false, + }, + triggerId: { + type: String, + optional: false, + }, + actionId: { + type: String, + optional: false, + }, + boardId: { + type: String, + optional: false, + }, +})); + +Rules.mutations({ + rename(description) { + return { $set: { description } }; + }, +}); + +Rules.helpers({ + getAction(){ + return Actions.findOne({_id:this.actionId}); + }, + getTrigger(){ + return Triggers.findOne({_id:this.triggerId}); + }, +}); + + +Rules.allow({ + insert(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + update(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + remove(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, +}); diff --git a/models/settings.js b/models/settings.js index 5d01f2ea..2f82e52f 100644 --- a/models/settings.js +++ b/models/settings.js @@ -129,6 +129,18 @@ if (Meteor.isServer) { } } + function isLdapEnabled() { + return process.env.LDAP_ENABLE === 'true'; + } + + function isOauth2Enabled() { + return process.env.OAUTH2_ENABLED === 'true'; + } + + function isCasEnabled() { + return process.env.CAS_ENABLED === 'true'; + } + Meteor.methods({ sendInvitation(emails, boards) { check(emails, [String]); @@ -198,5 +210,26 @@ if (Meteor.isServer) { withUserName: process.env.MATOMO_WITH_USERNAME || false, }; }, + + _isLdapEnabled() { + return isLdapEnabled(); + }, + + _isOauth2Enabled() { + return isOauth2Enabled(); + }, + + _isCasEnabled() { + return isCasEnabled(); + }, + + // Gets all connection methods to use it in the Template + getAuthenticationsEnabled() { + return { + ldap: isLdapEnabled(), + oauth2: isOauth2Enabled(), + cas: isCasEnabled(), + }; + }, }); } diff --git a/models/triggers.js b/models/triggers.js new file mode 100644 index 00000000..15982b6e --- /dev/null +++ b/models/triggers.js @@ -0,0 +1,58 @@ +Triggers = new Mongo.Collection('triggers'); + +Triggers.mutations({ + rename(description) { + return { + $set: { + description, + }, + }; + }, +}); + +Triggers.allow({ + insert(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + update(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, + remove(userId, doc) { + return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + }, +}); + +Triggers.helpers({ + + description() { + return this.desc; + }, + + getRule() { + return Rules.findOne({ + triggerId: this._id, + }); + }, + + fromList() { + return Lists.findOne(this.fromId); + }, + + toList() { + return Lists.findOne(this.toId); + }, + + findList(title) { + return Lists.findOne({ + title, + }); + }, + + labels() { + const boardLabels = this.board().labels; + const cardLabels = _.filter(boardLabels, (label) => { + return _.contains(this.labelIds, label._id); + }); + return cardLabels; + }, +}); diff --git a/models/users.js b/models/users.js index dc0ab608..4f2184e4 100644 --- a/models/users.js +++ b/models/users.js @@ -123,6 +123,11 @@ Users.attachSchema(new SimpleSchema({ type: Boolean, optional: true, }, + 'authenticationMethod': { + type: String, + optional: false, + defaultValue: 'password', + }, })); Users.allow({ @@ -484,29 +489,30 @@ if (Meteor.isServer) { return user; } - // if (user.services.oidc) { - // const email = user.services.oidc.email.toLowerCase(); - // - // user.username = user.services.oidc.username; - // user.emails = [{ address: email, verified: true }]; - // const initials = user.services.oidc.fullname.match(/\b[a-zA-Z]/g).join('').toUpperCase(); - // user.profile = { initials, fullname: user.services.oidc.fullname }; - // - // // see if any existing user has this email address or username, otherwise create new - // const existingUser = Meteor.users.findOne({$or: [{'emails.address': email}, {'username':user.username}]}); - // if (!existingUser) - // return user; - // - // // copy across new service info - // const service = _.keys(user.services)[0]; - // existingUser.services[service] = user.services[service]; - // existingUser.emails = user.emails; - // existingUser.username = user.username; - // existingUser.profile = user.profile; - // - // Meteor.users.remove({_id: existingUser._id}); // remove existing record - // return existingUser; - // } + if (user.services.oidc) { + const email = user.services.oidc.email.toLowerCase(); + user.username = user.services.oidc.username; + user.emails = [{ address: email, verified: true }]; + const initials = user.services.oidc.fullname.match(/\b[a-zA-Z]/g).join('').toUpperCase(); + user.profile = { initials, fullname: user.services.oidc.fullname }; + user.authenticationMethod = 'oauth2'; + + // see if any existing user has this email address or username, otherwise create new + const existingUser = Meteor.users.findOne({$or: [{'emails.address': email}, {'username':user.username}]}); + if (!existingUser) + return user; + + // copy across new service info + const service = _.keys(user.services)[0]; + existingUser.services[service] = user.services[service]; + existingUser.emails = user.emails; + existingUser.username = user.username; + existingUser.profile = user.profile; + existingUser.authenticationMethod = user.authenticationMethod; + + Meteor.users.remove({_id: existingUser._id}); // remove existing record + return existingUser; + } if (options.from === 'admin') { user.createdThroughApi = true; @@ -514,7 +520,10 @@ if (Meteor.isServer) { } const disableRegistration = Settings.findOne().disableRegistration; - if (!disableRegistration) { + // If ldap, bypass the inviation code if the self registration isn't allowed. + // TODO : pay attention if ldap field in the user model change to another content ex : ldap field to connection_type + if (options.ldap || !disableRegistration) { + user.authenticationMethod = 'ldap'; return user; } @@ -632,7 +641,9 @@ if (Meteor.isServer) { //invite user to corresponding boards const disableRegistration = Settings.findOne().disableRegistration; - if (disableRegistration) { + // If ldap, bypass the inviation code if the self registration isn't allowed. + // TODO : pay attention if ldap field in the user model change to another content ex : ldap field to connection_type + if (doc.authenticationMethod !== 'ldap' && disableRegistration) { const invitationCode = InvitationCodes.findOne({code: doc.profile.icode, valid: true}); if (!invitationCode) { throw new Meteor.Error('error-invitation-code-not-exist'); @@ -762,6 +773,81 @@ if (Meteor.isServer) { } }); + JsonRoutes.add('POST', '/api/boards/:boardId/members/:userId/add', function (req, res) { + try { + Authentication.checkUserId(req.userId); + const userId = req.params.userId; + const boardId = req.params.boardId; + const action = req.body.action; + const {isAdmin, isNoComments, isCommentOnly} = req.body; + let data = Meteor.users.findOne({ _id: userId }); + if (data !== undefined) { + if (action === 'add') { + data = Boards.find({ + _id: boardId, + }).map(function(board) { + if (!board.hasMember(userId)) { + board.addMember(userId); + function isTrue(data){ + return data.toLowerCase() === 'true'; + } + board.setMemberPermission(userId, isTrue(isAdmin), isTrue(isNoComments), isTrue(isCommentOnly), userId); + } + return { + _id: board._id, + title: board.title, + }; + }); + } + } + JsonRoutes.sendResult(res, { + code: 200, + data: query, + }); + } + catch (error) { + JsonRoutes.sendResult(res, { + code: 200, + data: error, + }); + } + }); + + JsonRoutes.add('POST', '/api/boards/:boardId/members/:userId/remove', function (req, res) { + try { + Authentication.checkUserId(req.userId); + const userId = req.params.userId; + const boardId = req.params.boardId; + const action = req.body.action; + let data = Meteor.users.findOne({ _id: userId }); + if (data !== undefined) { + if (action === 'remove') { + data = Boards.find({ + _id: boardId, + }).map(function(board) { + if (board.hasMember(userId)) { + board.removeMember(userId); + } + return { + _id: board._id, + title: board.title, + }; + }); + } + } + JsonRoutes.sendResult(res, { + code: 200, + data: query, + }); + } + catch (error) { + JsonRoutes.sendResult(res, { + code: 200, + data: error, + }); + } + }); + JsonRoutes.add('POST', '/api/users/', function (req, res) { try { Authentication.checkUserId(req.userId); diff --git a/models/wekanCreator.js b/models/wekanCreator.js index d144821f..59d0cfd5 100644 --- a/models/wekanCreator.js +++ b/models/wekanCreator.js @@ -1,4 +1,4 @@ -const DateString = Match.Where(function (dateAsString) { +const DateString = Match.Where(function(dateAsString) { check(dateAsString, String); return moment(dateAsString, moment.ISO_8601).isValid(); }); @@ -42,6 +42,10 @@ export class WekanCreator { this.comments = {}; // the members, indexed by Wekan member id => Wekan user ID this.members = data.membersMapping ? data.membersMapping : {}; + // Map of triggers Wekan ID => Wekan ID + this.triggers = {}; + // Map of actions Wekan ID => Wekan ID + this.actions = {}; // maps a wekanCardId to an array of wekanAttachments this.attachments = {}; @@ -57,10 +61,10 @@ export class WekanCreator { * @param {String} dateString a properly formatted Date */ _now(dateString) { - if(dateString) { + if (dateString) { return new Date(dateString); } - if(!this._nowDate) { + if (!this._nowDate) { this._nowDate = new Date(); } return this._nowDate; @@ -72,9 +76,9 @@ export class WekanCreator { * Otherwise return current logged user. * @param wekanUserId * @private - */ + */ _user(wekanUserId) { - if(wekanUserId && this.members[wekanUserId]) { + if (wekanUserId && this.members[wekanUserId]) { return this.members[wekanUserId]; } return Meteor.userId(); @@ -96,7 +100,7 @@ export class WekanCreator { // allowed values (is it worth the maintenance?) color: String, permission: Match.Where((value) => { - return ['private', 'public'].indexOf(value)>= 0; + return ['private', 'public'].indexOf(value) >= 0; }), })); } @@ -147,6 +151,30 @@ export class WekanCreator { })]); } + checkRules(wekanRules) { + check(wekanRules, [Match.ObjectIncluding({ + triggerId: String, + actionId: String, + title: String, + })]); + } + + checkTriggers(wekanTriggers) { + // XXX More check based on trigger type + check(wekanTriggers, [Match.ObjectIncluding({ + activityType: String, + desc: String, + })]); + } + + checkActions(wekanActions) { + // XXX More check based on action type + check(wekanActions, [Match.ObjectIncluding({ + actionType: String, + desc: String, + })]); + } + // You must call parseActions before calling this one. createBoardAndLabels(boardToImport) { const boardToCreate = { @@ -172,12 +200,12 @@ export class WekanCreator { title: boardToImport.title, }; // now add other members - if(boardToImport.members) { + if (boardToImport.members) { boardToImport.members.forEach((wekanMember) => { // do we already have it in our list? - if(!boardToCreate.members.some((member) => member.wekanId === wekanMember.wekanId)) + if (!boardToCreate.members.some((member) => member.wekanId === wekanMember.wekanId)) boardToCreate.members.push({ - ... wekanMember, + ...wekanMember, userId: wekanMember.wekanId, }); }); @@ -194,7 +222,11 @@ export class WekanCreator { boardToCreate.labels.push(labelToCreate); }); const boardId = Boards.direct.insert(boardToCreate); - Boards.direct.update(boardId, {$set: {modifiedAt: this._now()}}); + Boards.direct.update(boardId, { + $set: { + modifiedAt: this._now(), + }, + }); // log activity Activities.direct.insert({ activityType: 'importBoard', @@ -246,21 +278,21 @@ export class WekanCreator { }); } // add members { - if(card.members) { + if (card.members) { const wekanMembers = []; // we can't just map, as some members may not have been mapped card.members.forEach((sourceMemberId) => { - if(this.members[sourceMemberId]) { + if (this.members[sourceMemberId]) { const wekanId = this.members[sourceMemberId]; // we may map multiple Wekan members to the same wekan user // in which case we risk adding the same user multiple times - if(!wekanMembers.find((wId) => wId === wekanId)){ + if (!wekanMembers.find((wId) => wId === wekanId)) { wekanMembers.push(wekanId); } } return true; }); - if(wekanMembers.length>0) { + if (wekanMembers.length > 0) { cardToCreate.members = wekanMembers; } } @@ -321,9 +353,9 @@ export class WekanCreator { // - the template then tries to display the url to the attachment which causes other errors // so we make it server only, and let UI catch up once it is done, forget about latency comp. const self = this; - if(Meteor.isServer) { + if (Meteor.isServer) { if (att.url) { - file.attachData(att.url, function (error) { + file.attachData(att.url, function(error) { file.boardId = boardId; file.cardId = cardId; file.userId = self._user(att.userId); @@ -331,20 +363,26 @@ export class WekanCreator { // attachments' related activities automatically file.source = 'import'; if (error) { - throw(error); + throw (error); } else { const wekanAtt = Attachments.insert(file, () => { // we do nothing }); self.attachmentIds[att._id] = wekanAtt._id; // - if(wekanCoverId === att._id) { - Cards.direct.update(cardId, { $set: {coverId: wekanAtt._id}}); + if (wekanCoverId === att._id) { + Cards.direct.update(cardId, { + $set: { + coverId: wekanAtt._id, + }, + }); } } }); } else if (att.file) { - file.attachData(new Buffer(att.file, 'base64'), {type: att.type}, (error) => { + file.attachData(new Buffer(att.file, 'base64'), { + type: att.type, + }, (error) => { file.name(att.name); file.boardId = boardId; file.cardId = cardId; @@ -353,15 +391,19 @@ export class WekanCreator { // attachments' related activities automatically file.source = 'import'; if (error) { - throw(error); + throw (error); } else { const wekanAtt = Attachments.insert(file, () => { // we do nothing }); this.attachmentIds[att._id] = wekanAtt._id; // - if(wekanCoverId === att._id) { - Cards.direct.update(cardId, { $set: {coverId: wekanAtt._id}}); + if (wekanCoverId === att._id) { + Cards.direct.update(cardId, { + $set: { + coverId: wekanAtt._id, + }, + }); } } }); @@ -404,7 +446,11 @@ export class WekanCreator { sort: list.sort ? list.sort : listIndex, }; const listId = Lists.direct.insert(listToCreate); - Lists.direct.update(listId, {$set: {'updatedAt': this._now()}}); + Lists.direct.update(listId, { + $set: { + 'updatedAt': this._now(), + }, + }); this.lists[list._id] = listId; // // log activity // Activities.direct.insert({ @@ -437,7 +483,11 @@ export class WekanCreator { sort: swimlane.sort ? swimlane.sort : swimlaneIndex, }; const swimlaneId = Swimlanes.direct.insert(swimlaneToCreate); - Swimlanes.direct.update(swimlaneId, {$set: {'updatedAt': this._now()}}); + Swimlanes.direct.update(swimlaneId, { + $set: { + 'updatedAt': this._now(), + }, + }); this.swimlanes[swimlane._id] = swimlaneId; }); } @@ -459,6 +509,47 @@ export class WekanCreator { return result; } + createTriggers(wekanTriggers, boardId) { + wekanTriggers.forEach((trigger) => { + if (trigger.hasOwnProperty('labelId')) { + trigger.labelId = this.labels[trigger.labelId]; + } + if (trigger.hasOwnProperty('memberId')) { + trigger.memberId = this.members[trigger.memberId]; + } + trigger.boardId = boardId; + const oldId = trigger._id; + delete trigger._id; + this.triggers[oldId] = Triggers.direct.insert(trigger); + }); + } + + createActions(wekanActions, boardId) { + wekanActions.forEach((action) => { + if (action.hasOwnProperty('labelId')) { + action.labelId = this.labels[action.labelId]; + } + if (action.hasOwnProperty('memberId')) { + action.memberId = this.members[action.memberId]; + } + action.boardId = boardId; + const oldId = action._id; + delete action._id; + this.actions[oldId] = Actions.direct.insert(action); + }); + } + + createRules(wekanRules, boardId) { + wekanRules.forEach((rule) => { + // Create the rule + rule.boardId = boardId; + rule.triggerId = this.triggers[rule.triggerId]; + rule.actionId = this.actions[rule.actionId]; + delete rule._id; + Rules.direct.insert(rule); + }); + } + createChecklistItems(wekanChecklistItems) { wekanChecklistItems.forEach((checklistitem, checklistitemIndex) => { // Create the checklistItem @@ -477,7 +568,8 @@ export class WekanCreator { parseActivities(wekanBoard) { wekanBoard.activities.forEach((activity) => { switch (activity.activityType) { - case 'addAttachment': { + case 'addAttachment': + { // We have to be cautious, because the attachment could have been removed later. // In that case Wekan still reports its addition, but removes its 'url' field. // So we test for that @@ -485,12 +577,12 @@ export class WekanCreator { return attachment._id === activity.attachmentId; })[0]; - if ( typeof wekanAttachment !== 'undefined' && wekanAttachment ) { - if(wekanAttachment.url || wekanAttachment.file) { - // we cannot actually create the Wekan attachment, because we don't yet - // have the cards to attach it to, so we store it in the instance variable. + if (typeof wekanAttachment !== 'undefined' && wekanAttachment) { + if (wekanAttachment.url || wekanAttachment.file) { + // we cannot actually create the Wekan attachment, because we don't yet + // have the cards to attach it to, so we store it in the instance variable. const wekanCardId = activity.cardId; - if(!this.attachments[wekanCardId]) { + if (!this.attachments[wekanCardId]) { this.attachments[wekanCardId] = []; } this.attachments[wekanCardId].push(wekanAttachment); @@ -498,7 +590,8 @@ export class WekanCreator { } break; } - case 'addComment': { + case 'addComment': + { const wekanComment = wekanBoard.comments.filter((comment) => { return comment._id === activity.commentId; })[0]; @@ -509,26 +602,31 @@ export class WekanCreator { this.comments[id].push(wekanComment); break; } - case 'createBoard': { + case 'createBoard': + { this.createdAt.board = activity.createdAt; break; } - case 'createCard': { + case 'createCard': + { const cardId = activity.cardId; this.createdAt.cards[cardId] = activity.createdAt; this.createdBy.cards[cardId] = activity.userId; break; } - case 'createList': { + case 'createList': + { const listId = activity.listId; this.createdAt.lists[listId] = activity.createdAt; break; } - case 'createSwimlane': { + case 'createSwimlane': + { const swimlaneId = activity.swimlaneId; this.createdAt.swimlanes[swimlaneId] = activity.createdAt; break; - }} + } + } }); } @@ -537,7 +635,8 @@ export class WekanCreator { switch (activity.activityType) { // Board related activities // TODO: addBoardMember, removeBoardMember - case 'createBoard': { + case 'createBoard': + { Activities.direct.insert({ userId: this._user(activity.userId), type: 'board', @@ -550,7 +649,8 @@ export class WekanCreator { } // List related activities // TODO: removeList, archivedList - case 'createList': { + case 'createList': + { Activities.direct.insert({ userId: this._user(activity.userId), type: 'list', @@ -563,7 +663,8 @@ export class WekanCreator { } // Card related activities // TODO: archivedCard, restoredCard, joinMember, unjoinMember - case 'createCard': { + case 'createCard': + { Activities.direct.insert({ userId: this._user(activity.userId), activityType: activity.activityType, @@ -574,7 +675,8 @@ export class WekanCreator { }); break; } - case 'moveCard': { + case 'moveCard': + { Activities.direct.insert({ userId: this._user(activity.userId), oldListId: this.lists[activity.oldListId], @@ -587,7 +689,8 @@ export class WekanCreator { break; } // Comment related activities - case 'addComment': { + case 'addComment': + { Activities.direct.insert({ userId: this._user(activity.userId), activityType: activity.activityType, @@ -599,7 +702,8 @@ export class WekanCreator { break; } // Attachment related activities - case 'addAttachment': { + case 'addAttachment': + { Activities.direct.insert({ userId: this._user(activity.userId), type: 'card', @@ -612,7 +716,8 @@ export class WekanCreator { break; } // Checklist related activities - case 'addChecklist': { + case 'addChecklist': + { Activities.direct.insert({ userId: this._user(activity.userId), activityType: activity.activityType, @@ -623,7 +728,8 @@ export class WekanCreator { }); break; } - case 'addChecklistItem': { + case 'addChecklistItem': + { Activities.direct.insert({ userId: this._user(activity.userId), activityType: activity.activityType, @@ -636,7 +742,8 @@ export class WekanCreator { createdAt: this._now(activity.createdAt), }); break; - }} + } + } }); } @@ -652,6 +759,9 @@ export class WekanCreator { this.checkSwimlanes(board.swimlanes); this.checkCards(board.cards); this.checkChecklists(board.checklists); + this.checkRules(board.rules); + this.checkActions(board.actions); + this.checkTriggers(board.triggers); this.checkChecklistItems(board.checklistItems); } catch (e) { throw new Meteor.Error('error-json-schema'); @@ -674,6 +784,9 @@ export class WekanCreator { this.createChecklists(board.checklists); this.createChecklistItems(board.checklistItems); this.importActivities(board.activities, boardId); + this.createTriggers(board.triggers, boardId); + this.createActions(board.actions, boardId); + this.createRules(board.rules, boardId); // XXX add members return boardId; } diff --git a/package.json b/package.json index 6247c4a4..7bea1112 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "wekan", - "version": "1.55.0", - "description": "Open-Source kanban", + "version": "v1.55.1", + "description": "The open-source kanban", "private": true, "scripts": { "lint": "eslint --ignore-pattern 'packages/*' .", @@ -23,9 +23,11 @@ "eslint": "^4.19.1" }, "dependencies": { + "@babel/runtime": "^7.1.2", "babel-runtime": "^6.26.0", "bson-ext": "^2.0.0", "es6-promise": "^4.2.4", + "hoek": "^5.0.4", "meteor-node-stubs": "^0.4.1", "os": "^0.1.1", "page": "^1.8.6", diff --git a/sandstorm-pkgdef.capnp b/sandstorm-pkgdef.capnp index f66b6d0c..4c38b6f7 100644 --- a/sandstorm-pkgdef.capnp +++ b/sandstorm-pkgdef.capnp @@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = ( appTitle = (defaultText = "Wekan"), # The name of the app as it is displayed to the user. - appVersion = 154, + appVersion = 156, # Increment this for every release. - appMarketingVersion = (defaultText = "1.55.0~2018-10-16"), + appMarketingVersion = (defaultText = "1.55.1~2018-10-16"), # Human-readable presentation of the app version. minUpgradableAppVersion = 0, @@ -245,12 +245,14 @@ const myCommand :Spk.Manifest.Command = ( (key = "BROWSER_POLICY_ENABLED", value="true"), (key = "TRUSTED_URL", value=""), (key = "WEBHOOKS_ATTRIBUTES", value=""), - (key = "OAUTH2_CLIENT_ID", value=""), + (key = "OAUTH2_ENABLED", value=""), + (key = "OAUTH2_CLIENT_ID", value="false"), (key = "OAUTH2_SECRET", value=""), (key = "OAUTH2_SERVER_URL", value=""), (key = "OAUTH2_AUTH_ENDPOINT", value=""), (key = "OAUTH2_USERINFO_ENDPOINT", value=""), (key = "OAUTH2_TOKEN_ENDPOINT", value=""), + (key = "LDAP_ENABLE", value="false"), (key = "SANDSTORM", value = "1"), (key = "METEOR_SETTINGS", value = "{\"public\": {\"sandstorm\": true}}") ] diff --git a/server/authentication.js b/server/authentication.js index 8a74ebf7..6310e8df 100644 --- a/server/authentication.js +++ b/server/authentication.js @@ -62,27 +62,28 @@ Meteor.startup(() => { Authentication.checkAdminOrCondition(userId, normalAccess); }; -// if (Meteor.isServer) { -// -// if(process.env.OAUTH2_CLIENT_ID !== '') { -// -// ServiceConfiguration.configurations.upsert( // eslint-disable-line no-undef -// { service: 'oidc' }, -// { -// $set: { -// loginStyle: 'redirect', -// clientId: process.env.OAUTH2_CLIENT_ID, -// secret: process.env.OAUTH2_SECRET, -// serverUrl: process.env.OAUTH2_SERVER_URL, -// authorizationEndpoint: process.env.OAUTH2_AUTH_ENDPOINT, -// userinfoEndpoint: process.env.OAUTH2_USERINFO_ENDPOINT, -// tokenEndpoint: process.env.OAUTH2_TOKEN_ENDPOINT, -// idTokenWhitelistFields: [], -// requestPermissions: ['openid'], -// }, -// } -// ); -// } -// } + if (Meteor.isServer) { + + if(process.env.OAUTH2_CLIENT_ID !== '') { + + ServiceConfiguration.configurations.upsert( // eslint-disable-line no-undef + { service: 'oidc' }, + { + $set: { + loginStyle: 'redirect', + clientId: process.env.OAUTH2_CLIENT_ID, + secret: process.env.OAUTH2_SECRET, + serverUrl: process.env.OAUTH2_SERVER_URL, + authorizationEndpoint: process.env.OAUTH2_AUTH_ENDPOINT, + userinfoEndpoint: process.env.OAUTH2_USERINFO_ENDPOINT, + tokenEndpoint: process.env.OAUTH2_TOKEN_ENDPOINT, + idTokenWhitelistFields: [], + requestPermissions: ['openid'], + }, + } + ); + } + } }); + diff --git a/server/migrations.js b/server/migrations.js index ac33d836..2ccda54d 100644 --- a/server/migrations.js +++ b/server/migrations.js @@ -322,6 +322,18 @@ Migrations.add('add-subtasks-allowed', () => { }, noValidateMulti); }); +Migrations.add('add-authenticationMethod', () => { + Users.update({ + 'authenticationMethod': { + $exists: false, + }, + }, { + $set: { + 'authenticationMethod': 'password', + }, + }, noValidateMulti); +}); + Migrations.add('remove-tag', () => { Users.update({ }, { @@ -338,4 +350,3 @@ Migrations.add('remove-customFields-references-broken', () => { }, }, noValidateMulti); }); - diff --git a/server/notifications/email.js b/server/notifications/email.js index 2af6381e..b2b7fab8 100644 --- a/server/notifications/email.js +++ b/server/notifications/email.js @@ -39,3 +39,5 @@ Meteor.startup(() => { }, 30000); }); }); + + diff --git a/server/notifications/notifications.js b/server/notifications/notifications.js index 19d43bc4..fa8b2ee2 100644 --- a/server/notifications/notifications.js +++ b/server/notifications/notifications.js @@ -19,7 +19,6 @@ Notifications = { delete notifyServices[serviceName]; }, - // filter recipients according to user settings for notification getUsers: (watchers) => { const users = []; watchers.forEach((userId) => { diff --git a/server/publications/people.js b/server/publications/people.js index 7c13bdcc..56187732 100644 --- a/server/publications/people.js +++ b/server/publications/people.js @@ -17,6 +17,7 @@ Meteor.publish('people', function(limit) { 'emails': 1, 'createdAt': 1, 'loginDisabled': 1, + 'authenticationMethod': 1, }, }); } else { diff --git a/server/publications/rules.js b/server/publications/rules.js new file mode 100644 index 00000000..d0864893 --- /dev/null +++ b/server/publications/rules.js @@ -0,0 +1,18 @@ +Meteor.publish('rules', (ruleId) => { + check(ruleId, String); + return Rules.find({ + _id: ruleId, + }); +}); + +Meteor.publish('allRules', () => { + return Rules.find({}); +}); + +Meteor.publish('allTriggers', () => { + return Triggers.find({}); +}); + +Meteor.publish('allActions', () => { + return Actions.find({}); +}); diff --git a/server/publications/users.js b/server/publications/users.js index 4fd98e13..f0c94153 100644 --- a/server/publications/users.js +++ b/server/publications/users.js @@ -17,3 +17,12 @@ Meteor.publish('user-admin', function() { }, }); }); + +Meteor.publish('user-authenticationMethod', function(match) { + check(match, String); + return Users.find({$or: [{_id: match}, {email: match}, {username: match}]}, { + fields: { + 'authenticationMethod': 1, + }, + }); +}); diff --git a/server/rulesHelper.js b/server/rulesHelper.js new file mode 100644 index 00000000..833092d0 --- /dev/null +++ b/server/rulesHelper.js @@ -0,0 +1,138 @@ +RulesHelper = { + executeRules(activity){ + const matchingRules = this.findMatchingRules(activity); + for(let i = 0; i< matchingRules.length; i++){ + const action = matchingRules[i].getAction(); + if(action !== undefined){ + this.performAction(activity, action); + } + } + }, + findMatchingRules(activity){ + const activityType = activity.activityType; + if(TriggersDef[activityType] === undefined){ + return []; + } + const matchingFields = TriggersDef[activityType].matchingFields; + const matchingMap = this.buildMatchingFieldsMap(activity, matchingFields); + const matchingTriggers = Triggers.find(matchingMap); + const matchingRules = []; + matchingTriggers.forEach(function(trigger){ + const rule = trigger.getRule(); + // Check that for some unknown reason there are some leftover triggers + // not connected to any rules + if(rule !== undefined){ + matchingRules.push(trigger.getRule()); + } + }); + return matchingRules; + }, + buildMatchingFieldsMap(activity, matchingFields){ + const matchingMap = {'activityType':activity.activityType}; + for(let i = 0; i< matchingFields.length; i++){ + // Creating a matching map with the actual field of the activity + // and with the wildcard (for example: trigger when a card is added + // in any [*] board + matchingMap[matchingFields[i]] = { $in: [activity[matchingFields[i]], '*']}; + } + return matchingMap; + }, + performAction(activity, action){ + const card = Cards.findOne({_id:activity.cardId}); + const boardId = activity.boardId; + if(action.actionType === 'moveCardToTop'){ + let listId; + let list; + if(action.listTitle === '*'){ + listId = card.listId; + list = card.list(); + }else{ + list = Lists.findOne({title: action.listTitle, boardId }); + listId = list._id; + } + const minOrder = _.min(list.cardsUnfiltered(card.swimlaneId).map((c) => c.sort)); + card.move(card.swimlaneId, listId, minOrder - 1); + } + if(action.actionType === 'moveCardToBottom'){ + let listId; + let list; + if(action.listTitle === '*'){ + listId = card.listId; + list = card.list(); + }else{ + list = Lists.findOne({title: action.listTitle, boardId}); + listId = list._id; + } + const maxOrder = _.max(list.cardsUnfiltered(card.swimlaneId).map((c) => c.sort)); + card.move(card.swimlaneId, listId, maxOrder + 1); + } + if(action.actionType === 'sendEmail'){ + const emailTo = action.emailTo; + const emailMsg = action.emailMsg; + const emailSubject = action.emailSubject; + try { + Email.send({ + emailTo, + from: Accounts.emailTemplates.from, + emailSubject, + emailMsg, + }); + } catch (e) { + return; + } + } + if(action.actionType === 'archive'){ + card.archive(); + } + if(action.actionType === 'unarchive'){ + card.restore(); + } + if(action.actionType === 'addLabel'){ + card.addLabel(action.labelId); + } + if(action.actionType === 'removeLabel'){ + card.removeLabel(action.labelId); + } + if(action.actionType === 'addMember'){ + const memberId = Users.findOne({username:action.username})._id; + card.assignMember(memberId); + } + if(action.actionType === 'removeMember'){ + if(action.memberName === '*'){ + const members = card.members; + for(let i = 0; i< members.length; i++){ + card.unassignMember(members[i]); + } + }else{ + const memberId = Users.findOne({username:action.username})._id; + card.unassignMember(memberId); + } + } + if(action.actionType === 'checkAll'){ + const checkList = Checklists.findOne({'title':action.checklistName, 'cardId':card._id}); + checkList.checkAllItems(); + } + if(action.actionType === 'uncheckAll'){ + const checkList = Checklists.findOne({'title':action.checklistName, 'cardId':card._id}); + checkList.uncheckAllItems(); + } + if(action.actionType === 'checkItem'){ + const checkList = Checklists.findOne({'title':action.checklistName, 'cardId':card._id}); + const checkItem = ChecklistItems.findOne({'title':action.checkItemName, 'checkListId':checkList._id}); + checkItem.check(); + } + if(action.actionType === 'uncheckItem'){ + const checkList = Checklists.findOne({'title':action.checklistName, 'cardId':card._id}); + const checkItem = ChecklistItems.findOne({'title':action.checkItemName, 'checkListId':checkList._id}); + checkItem.uncheck(); + } + if(action.actionType === 'addChecklist'){ + Checklists.insert({'title':action.checklistName, 'cardId':card._id, 'sort':0}); + } + if(action.actionType === 'removeChecklist'){ + Checklists.remove({'title':action.checklistName, 'cardId':card._id, 'sort':0}); + } + + }, + +}; diff --git a/server/triggersDef.js b/server/triggersDef.js new file mode 100644 index 00000000..f6d5333b --- /dev/null +++ b/server/triggersDef.js @@ -0,0 +1,58 @@ +TriggersDef = { + createCard:{ + matchingFields: ['boardId', 'listName'], + }, + moveCard:{ + matchingFields: ['boardId', 'listName', 'oldListName'], + }, + archivedCard:{ + matchingFields: ['boardId'], + }, + restoredCard:{ + matchingFields: ['boardId'], + }, + joinMember:{ + matchingFields: ['boardId', 'username'], + }, + unjoinMember:{ + matchingFields: ['boardId', 'username'], + }, + addChecklist:{ + matchingFields: ['boardId', 'checklistName'], + }, + removeChecklist:{ + matchingFields: ['boardId', 'checklistName'], + }, + completeChecklist:{ + matchingFields: ['boardId', 'checklistName'], + }, + uncompleteChecklist:{ + matchingFields: ['boardId', 'checklistName'], + }, + addedChecklistItem:{ + matchingFields: ['boardId', 'checklistItemName'], + }, + removedChecklistItem:{ + matchingFields: ['boardId', 'checklistItemName'], + }, + checkedItem:{ + matchingFields: ['boardId', 'checklistItemName'], + }, + uncheckedItem:{ + matchingFields: ['boardId', 'checklistItemName'], + }, + addAttachment:{ + matchingFields: ['boardId'], + }, + deleteAttachment:{ + matchingFields: ['boardId'], + }, + addedLabel:{ + matchingFields: ['boardId', 'labelId'], + }, + removedLabel:{ + matchingFields: ['boardId', 'labelId'], + }, +}; + + diff --git a/snap-src/bin/config b/snap-src/bin/config index a54b13c2..b81925ac 100755 --- a/snap-src/bin/config +++ b/snap-src/bin/config @@ -3,7 +3,7 @@ # All supported keys are defined here together with descriptions and default values # list of supported keys -keys="MONGODB_BIND_UNIX_SOCKET MONGODB_BIND_IP MONGODB_PORT MAIL_URL MAIL_FROM ROOT_URL PORT DISABLE_MONGODB CADDY_ENABLED CADDY_BIND_PORT WITH_API MATOMO_ADDRESS MATOMO_SITE_ID MATOMO_DO_NOT_TRACK MATOMO_WITH_USERNAME BROWSER_POLICY_ENABLED TRUSTED_URL WEBHOOKS_ATTRIBUTES OAUTH2_CLIENT_ID OAUTH2_SECRET OAUTH2_SERVER_URL OAUTH2_AUTH_ENDPOINT OAUTH2_USERINFO_ENDPOINT OAUTH2_TOKEN_ENDPOINT" +keys="MONGODB_BIND_UNIX_SOCKET MONGODB_BIND_IP MONGODB_PORT MAIL_URL MAIL_FROM ROOT_URL PORT DISABLE_MONGODB CADDY_ENABLED CADDY_BIND_PORT WITH_API MATOMO_ADDRESS MATOMO_SITE_ID MATOMO_DO_NOT_TRACK MATOMO_WITH_USERNAME BROWSER_POLICY_ENABLED TRUSTED_URL WEBHOOKS_ATTRIBUTES OAUTH2_ENABLED OAUTH2_CLIENT_ID OAUTH2_SECRET OAUTH2_SERVER_URL OAUTH2_AUTH_ENDPOINT OAUTH2_USERINFO_ENDPOINT OAUTH2_TOKEN_ENDPOINT LDAP_ENABLE LDAP_PORT LDAP_HOST LDAP_BASEDN LDAP_LOGIN_FALLBACK LDAP_RECONNECT LDAP_TIMEOUT LDAP_IDLE_TIMEOUT LDAP_CONNECT_TIMEOUT LDAP_AUTHENTIFICATION LDAP_AUTHENTIFICATION_USERDN LDAP_AUTHENTIFICATION_PASSWORD LDAP_LOG_ENABLED LDAP_BACKGROUND_SYNC LDAP_BACKGROUND_SYNC_INTERVAL LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS LDAP_ENCRYPTION LDAP_CA_CERT LDAP_REJECT_UNAUTHORIZED LDAP_USER_SEARCH_FILTER LDAP_USER_SEARCH_SCOPE LDAP_USER_SEARCH_FIELD LDAP_SEARCH_PAGE_SIZE LDAP_SEARCH_SIZE_LIMIT LDAP_GROUP_FILTER_ENABLE LDAP_GROUP_FILTER_OBJECTCLASS LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT LDAP_GROUP_FILTER_GROUP_NAME LDAP_UNIQUE_IDENTIFIER_FIELD LDAP_UTF8_NAMES_SLUGIFY LDAP_USERNAME_FIELD LDAP_MERGE_EXISTING_USERS LDAP_SYNC_USER_DATA LDAP_SYNC_USER_DATA_FIELDMAP LDAP_SYNC_GROUP_ROLES LDAP_DEFAULT_DOMAIN" # default values DESCRIPTION_MONGODB_BIND_UNIX_SOCKET="mongodb binding unix socket:\n"\ @@ -13,11 +13,11 @@ DEFAULT_MONGODB_BIND_UNIX_SOCKET="$SNAP_DATA/share" KEY_MONGODB_BIND_UNIX_SOCKET="mongodb-bind-unix-socket" DESCRIPTION_MONGODB_PORT="mongodb binding port: eg 27017 when using localhost" -DEFAULT_MONGODB_PORT="27019" +DEFAULT_MONGODB_PORT="" KEY_MONGODB_PORT='mongodb-port' DESCRIPTION_MONGODB_BIND_IP="mongodb binding ip address: eg 127.0.0.1 for localhost\n\t\tIf not defined default unix socket is used instead" -DEFAULT_MONGODB_BIND_IP="127.0.0.1" +DEFAULT_MONGODB_BIND_IP="" KEY_MONGODB_BIND_IP="mongodb-bind-ip" DESCRIPTION_MAIL_URL="wekan mail binding" @@ -82,6 +82,10 @@ DESCRIPTION_WEBHOOKS_ATTRIBUTES="What to send to Outgoing Webhook, or leave out. DEFAULT_WEBHOOKS_ATTRIBUTES="" KEY_WEBHOOKS_ATTRIBUTES="webhooks-attributes" +DESCRIPTION_OAUTH2_ENABLED="Enable the OAuth2 connection" +DEFAULT_OAUTH2_ENABLED="false" +KEY_OAUTH2_ENABLED="oauth2-enabled" + DESCRIPTION_OAUTH2_CLIENT_ID="OAuth2 Client ID, for example from Rocket.Chat. Example: abcde12345" DEFAULT_OAUTH2_CLIENT_ID="" KEY_OAUTH2_CLIENT_ID="oauth2-client-id" @@ -106,3 +110,158 @@ DESCRIPTION_OAUTH2_TOKEN_ENDPOINT="OAuth2 token endpoint. Example: /oauth/token" DEFAULT_OAUTH2_TOKEN_ENDPOINT="" KEY_OAUTH2_TOKEN_ENDPOINT="oauth2-token-endpoint" +DESCRIPTION_LDAP_ENABLE="Enable or not the connection by the LDAP" +DEFAULT_LDAP_ENABLE="false" +KEY_LDAP_ENABLE="ldap-enable" + +DESCRIPTION_LDAP_PORT="The port of the LDAP server" +DEFAULT_LDAP_PORT="389" +KEY_LDAP_PORT="ldap-port" + +DESCRIPTION_LDAP_HOST="The host server for the LDAP server" +DEFAULT_LDAP_HOST="" +KEY_LDAP_HOST="ldap-host" + +DESCRIPTION_LDAP_BASEDN="The base DN for the LDAP Tree" +DEFAULT_LDAP_BASEDN="" +KEY_LDAP_BASEDN="ldap-basedn" + +DESCRIPTION_LDAP_LOGIN_FALLBACK="Fallback on the default authentication method" +DEFAULT_LDAP_LOGIN_FALLBACK="false" +KEY_LDAP_LOGIN_FALLBACK="ldap-login-fallback" + +DESCRIPTION_LDAP_RECONNECT="Reconnect to the server if the connection is lost" +DEFAULT_LDAP_RECONNECT="true" +KEY_LDAP_RECONNECT="ldap-reconnect" + +DESCRIPTION_LDAP_TIMEOUT="Overall timeout, in milliseconds." +DEFAULT_LDAP_TIMEOUT="10000" +KEY_LDAP_TIMEOUT="ldap-timeout" + +DESCRIPTION_LDAP_IDLE_TIMEOUT="Specifies the timeout for idle LDAP connections in milliseconds" +DEFAULT_LDAP_IDLE_TIMEOUT="10000" +KEY_LDAP_IDLE_TIMEOUT="ldap-idle-timeout" + +DESCRIPTION_LDAP_CONNECT_TIMEOUT="Connection timeout, in milliseconds." +DEFAULT_LDAP_CONNECT_TIMEOUT="10000" +KEY_LDAP_CONNECT_TIMEOUT="ldap-connect-timeout" + +DESCRIPTION_LDAP_AUTHENTIFICATION="If the LDAP needs a user account to search" +DEFAULT_LDAP_AUTHENTIFICATION="false" +KEY_LDAP_AUTHENTIFICATION="ldap-authentication" + +DESCRIPTION_LDAP_AUTHENTIFICATION_USERDN="The search user DN" +DEFAULT_LDAP_AUTHENTIFICATION_USERDN="" +KEY_LDAP_AUTHENTIFICATION_USERDN="ldap-authentication-userdn" + +DESCRIPTION_LDAP_AUTHENTIFICATION_PASSWORD="The password for the search user" +DEFAULT_LDAP_AUTHENTIFICATION_PASSWORD="" +KEY_LDAP_AUTHENTIFICATION_PASSWORD="ldap-authentication-password" + +DESCRIPTION_LDAP_LOG_ENABLED="Enable logs for the module" +DEFAULT_LDAP_LOG_ENABLED="false" +KEY_LDAP_LOG_ENABLED="ldap-log-enabled" + +DESCRIPTION_LDAP_BACKGROUND_SYNC="If the sync of the users should be done in the background" +DEFAULT_LDAP_BACKGROUND_SYNC="false" +KEY_LDAP_BACKGROUND_SYNC="ldap-background-sync" + +DESCRIPTION_LDAP_BACKGROUND_SYNC_INTERVAL="At which interval does the background task sync in milliseconds" +DEFAULT_LDAP_BACKGROUND_SYNC_INTERVAL="100" +KEY_LDAP_BACKGROUND_SYNC_INTERVAL="ldap-background-sync-interval" + +DESCRIPTION_LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED="" +DEFAULT_LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED="false" +KEY_LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED="ldap-background-sync-keep-existant-users-updated" + +DESCRIPTION_LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS="" +DEFAULT_LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS="false" +KEY_LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS="ldap-background-sync-import-new-users" + +DESCRIPTION_LDAP_ENCRYPTION="If using LDAPS" +DEFAULT_LDAP_ENCRYPTION="false" +KEY_LDAP_ENCRYPTION="ldap-encryption" + +DESCRIPTION_LDAP_CA_CERT="The certification for the LDAPS server" +DEFAULT_LDAP_CA_CERT="" +KEY_LDAP_CA_CERT="ldap-ca-cert" + +DESCRIPTION_LDAP_REJECT_UNAUTHORIZED="Reject Unauthorized Certificate" +DEFAULT_LDAP_REJECT_UNAUTHORIZED="false" +KEY_LDAP_REJECT_UNAUTHORIZED="ldap-reject-unauthorized" + +DESCRIPTION_LDAP_USER_SEARCH_FILTER="Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed" +DEFAULT_LDAP_USER_SEARCH_FILTER="" +KEY_LDAP_USER_SEARCH_FILTER="ldap-user-search-filter" + +DESCRIPTION_LDAP_USER_SEARCH_SCOPE="Base (search only in the provided DN), one (search only in the provided DN and one level deep), or subtree (search the whole subtree)." +DEFAULT_LDAP_USER_SEARCH_SCOPE="" +KEY_LDAP_USER_SEARCH_SCOPE="ldap-user-search-scope" + +DESCRIPTION_LDAP_USER_SEARCH_FIELD="Which field is used to find the user" +DEFAULT_LDAP_USER_SEARCH_FIELD="" +KEY_LDAP_USER_SEARCH_FIELD="ldap-user-search-field" + +DESCRIPTION_LDAP_SEARCH_PAGE_SIZE="Used for pagination (0=unlimited)" +DEFAULT_LDAP_SEARCH_PAGE_SIZE="0" +KEY_LDAP_SEARCH_PAGE_SIZE="ldap-search-page-size" + +DESCRIPTION_LDAP_SEARCH_SIZE_LIMIT="The limit number of entries (0=unlimited)" +DEFAULT_LDAP_SEARCH_SIZE_LIMIT="0" +KEY_LDAP_SEARCH_SIZE_LIMIT="ldap-search-size-limit" + +DESCRIPTION_LDAP_GROUP_FILTER_ENABLE="Enable group filtering" +DEFAULT_LDAP_GROUP_FILTER_ENABLE="false" +KEY_LDAP_GROUP_FILTER_ENABLE="ldap-group-filter-enable" + +DESCRIPTION_LDAP_GROUP_FILTER_OBJECTCLASS="The object class for filtering" +DEFAULT_LDAP_GROUP_FILTER_OBJECTCLASS="" +KEY_LDAP_GROUP_FILTER_OBJECTCLASS="ldap-group-filter-objectclass" + +DESCRIPTION_LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE="" +DEFAULT_LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE="" +KEY_LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE="ldap-group-filter-id-attribute" + +DESCRIPTION_LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE="" +DEFAULT_LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE="" +KEY_LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE="ldap-group-filter-member-attribute" + +DESCRIPTION_LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT="" +DEFAULT_LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT="" +KEY_LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT="ldap-group-filter-member-format" + +DESCRIPTION_LDAP_GROUP_FILTER_GROUP_NAME="" +DEFAULT_LDAP_GROUP_FILTER_GROUP_NAME="" +KEY_LDAP_GROUP_FILTER_GROUP_NAME="ldap-group-filter-member-format" + +DESCRIPTION_LDAP_UNIQUE_IDENTIFIER_FIELD="This field is sometimes class GUID (Globally Unique Identifier)" +DEFAULT_LDAP_UNIQUE_IDENTIFIER_FIELD="" +KEY_LDAP_UNIQUE_IDENTIFIER_FIELD="ldap-unique-identifier-field" + +DESCRIPTION_LDAP_UTF8_NAMES_SLUGIFY="Convert the username to utf8" +DEFAULT_LDAP_UTF8_NAMES_SLUGIFY="true" +KEY_LDAP_UTF8_NAMES_SLUGIFY="ldap-utf8-names-slugify" + +DESCRIPTION_LDAP_USERNAME_FIELD="Which field contains the ldap username" +DEFAULT_LDAP_USERNAME_FIELD="" +KEY_LDAP_USERNAME_FIELD="ldap-username-field" + +DESCRIPTION_LDAP_MERGE_EXISTING_USERS="" +DEFAULT_LDAP_MERGE_EXISTING_USERS="false" +KEY_LDAP_MERGE_EXISTING_USERS="ldap-merge-existing-users" + +DESCRIPTION_LDAP_SYNC_USER_DATA="" +DEFAULT_LDAP_SYNC_USER_DATA="false" +KEY_LDAP_SYNC_USER_DATA="ldap-sync-user-data" + +DESCRIPTION_LDAP_SYNC_USER_DATA_FIELDMAP="" +DEFAULT_LDAP_SYNC_USER_DATA_FIELDMAP="" +KEY_LDAP_SYNC_USER_DATA_FIELDMAP="ldap-sync-user-data-fieldmap" + +DESCRIPTION_LDAP_SYNC_GROUP_ROLES="" +DEFAULT_LDAP_SYNC_GROUP_ROLES="" +KEY_LDAP_SYNC_GROUP_ROLES="ldap-sync-group-roles" + +DESCRIPTION_LDAP_DEFAULT_DOMAIN="The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP" +DEFAULT_LDAP_DEFAULT_DOMAIN="" +KEY_LDAP_DEFAULT_DOMAIN="ldap-default-domain" diff --git a/snap-src/bin/wekan-help b/snap-src/bin/wekan-help index 95814d36..3af19336 100755 --- a/snap-src/bin/wekan-help +++ b/snap-src/bin/wekan-help @@ -95,6 +95,156 @@ echo -e "\t$ snap set $SNAP_NAME OAUTH2_TOKEN_ENDPOINT='/oauth/token'" echo -e "\t-Disable the OAuth2 Token Endpoint of Wekan:" echo -e "\t$ snap set $SNAP_NAME OAUTH2_TOKEN_ENDPOINT=''" echo -e "\n" +echo -e "Ldap Enable." +echo -e "To enable the ldap of Wekan:" +echo -e "\t$ snap set $SNAP_NAME LDAP_ENABLE='true'" +echo -e "\t-Disable the ldap of Wekan:" +echo -e "\t$ snap set $SNAP_NAME LDAP_ENABLE='false'" +echo -e "\n" +echo -e "Ldap Port." +echo -e "The port of the ldap server:" +echo -e "\t$ snap set $SNAP_NAME LDAP_PORT='12345'" +echo -e "\n" +echo -e "Ldap Host." +echo -e "The host server for the LDAP server:" +echo -e "\t$ snap set $SNAP_NAME LDAP_HOST='localhost'" +echo -e "\n" +echo -e "Ldap Base Dn." +echo -e "The base DN for the LDAP Tree:" +echo -e "\t$ snap set $SNAP_NAME LDAP_BASEDN='ou=user,dc=example,dc=org'" +echo -e "\n" +echo -e "Ldap Login Fallback." +echo -e "Fallback on the default authentication method:" +echo -e "\t$ snap set $SNAP_NAME LDAP_LOGIN_FALLBACK='true'" +echo -e "\n" +echo -e "Ldap Reconnect." +echo -e "Reconnect to the server if the connection is lost:" +echo -e "\t$ snap set $SNAP_NAME LDAP_RECONNECT='false'" +echo -e "\n" +echo -e "Ldap Timeout." +echo -e "Overall timeout, in milliseconds:" +echo -e "\t$ snap set $SNAP_NAME LDAP_TIMEOUT='12345'" +echo -e "\n" +echo -e "Ldap Idle Timeout." +echo -e "Specifies the timeout for idle LDAP connections in milliseconds:" +echo -e "\t$ snap set $SNAP_NAME LDAP_IDLE_TIMEOUT='12345'" +echo -e "\n" +echo -e "Ldap Connect Timeout." +echo -e "Connection timeout, in milliseconds:" +echo -e "\t$ snap set $SNAP_NAME LDAP_CONNECT_TIMEOUT='12345'" +echo -e "\n" +echo -e "Ldap Authentication." +echo -e "If the LDAP needs a user account to search:" +echo -e "\t$ snap set $SNAP_NAME LDAP_AUTHENTIFICATION='true'" +echo -e "\n" +echo -e "Ldap Authentication User Dn." +echo -e "The search user Dn:" +echo -e "\t$ snap set $SNAP_NAME LDAP_AUTHENTIFICATION_USERDN='cn=admin,dc=example,dc=org'" +echo -e "\n" +echo -e "Ldap Authentication Password." +echo -e "The password for the search user:" +echo -e "\t$ snap set $SNAP_NAME AUTHENTIFICATION_PASSWORD='admin'" +echo -e "\n" +echo -e "Ldap Log Enabled." +echo -e "Enable logs for the module:" +echo -e "\t$ snap set $SNAP_NAME LDAP_LOG_ENABLED='true'" +echo -e "\n" +echo -e "Ldap Background Sync." +echo -e "If the sync of the users should be done in the background:" +echo -e "\t$ snap set $SNAP_NAME LDAP_BACKGROUND_SYNC='true'" +echo -e "\n" +echo -e "Ldap Background Sync Interval." +echo -e "At which interval does the background task sync in milliseconds:" +echo -e "\t$ snap set $SNAP_NAME LDAP_BACKGROUND_SYNC_INTERVAL='12345'" +echo -e "\n" +echo -e "Ldap Background Sync Keep Existant Users Updated." +echo -e "\t$ snap set $SNAP_NAME LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED='true'" +echo -e "\n" +echo -e "Ldap Background Sync Import New Users." +echo -e "\t$ snap set $SNAP_NAME LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS='true'" +echo -e "\n" +echo -e "Ldap Encryption." +echo -e "Allow LDAPS:" +echo -e "\t$ snap set $SNAP_NAME LDAP_ENCRYPTION='true'" +echo -e "\n" +echo -e "Ldap Ca Cert." +echo -e "The certification for the LDAPS server:" +echo -e "\t$ snap set $SNAP_NAME LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+zCCA+OgAwIBAgIkAhwR/6TVLmdRY6hHxvUFWc0+Enmu/Hu6cj+G2FIdAgIC...-----END CERTIFICATE-----" +echo -e "\n" +echo -e "Ldap Reject Unauthorized." +echo -e "Reject Unauthorized Certificate:" +echo -e "\t$ snap set $SNAP_NAME LDAP_REJECT_UNAUTHORIZED='true'" +echo -e "\n" +echo -e "Ldap User Search Filter." +echo -e "Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed:" +echo -e "\t$ snap set $SNAP_NAME LDAP_USER_SEARCH_FILTER=''" +echo -e "\n" +echo -e "Ldap User Search Scope." +echo -e "Base (search only in the provided DN), one (search only in the provided DN and one level deep), or subtree (search the whole subtree):" +echo -e "\t$ snap set $SNAP_NAME LDAP_USER_SEARCH_SCOPE=one" +echo -e "\n" +echo -e "Ldap User Search Field." +echo -e "Which field is used to find the user:" +echo -e "\t$ snap set $SNAP_NAME LDAP_USER_SEARCH_FIELD='uid'" +echo -e "\n" +echo -e "Ldap Search Page Size." +echo -e "Used for pagination (0=unlimited):" +echo -e "\t$ snap set $SNAP_NAME LDAP_SEARCH_PAGE_SIZE='12345'" +echo -e "\n" +echo -e "Ldap Search Size Limit." +echo -e "The limit number of entries (0=unlimited):" +echo -e "\t$ snap set $SNAP_NAME LDAP_SEARCH_SIZE_LIMIT='12345'" +echo -e "\n" +echo -e "Ldap Group Filter Enable." +echo -e "Enable group filtering:" +echo -e "\t$ snap set $SNAP_NAME LDAP_GROUP_FILTER_ENABLE='true'" +echo -e "\n" +echo -e "Ldap Group Filter ObjectClass." +echo -e "The object class for filtering:" +echo -e "\t$ snap set $SNAP_NAME LDAP_GROUP_FILTER_OBJECTCLASS='group'" +echo -e "\n" +echo -e "Ldap Group Filter Id Attribute." +echo -e "\t$ snap set $SNAP_NAME LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE=''" +echo -e "\n" +echo -e "Ldap Group Filter Member Attribute." +echo -e "\t$ snap set $SNAP_NAME LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE=''" +echo -e "\n" +echo -e "Ldap Group Filter Member Format." +echo -e "\t$ snap set $SNAP_NAME LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT=''" +echo -e "\n" +echo -e "Ldap Group Filter Group Name." +echo -e "\t$ snap set $SNAP_NAME LDAP_GROUP_FILTER_GROUP_NAME=''" +echo -e "\n" +echo -e "Ldap Unique Identifier Field." +echo -e "This field is sometimes class GUID (Globally Unique Identifier):" +echo -e "\t$ snap set $SNAP_NAME LDAP_UNIQUE_IDENTIFIER_FIELD=guid" +echo -e "\n" +echo -e "Ldap Utf8 Names Slugify." +echo -e "Convert the username to utf8:" +echo -e "\t$ snap set $SNAP_NAME LDAP_UTF8_NAMES_SLUGIFY='false'" +echo -e "\n" +echo -e "Ldap Username Field." +echo -e "Which field contains the ldap username:" +echo -e "\t$ snap set $SNAP_NAME LDAP_USERNAME_FIELD='username'" +echo -e "\n" +echo -e "Ldap Merge Existing Users." +echo -e "\t$ snap set $SNAP_NAME LDAP_MERGE_EXISTING_USERS='true'" +echo -e "\n" +echo -e "Ldap Sync User Data." +echo -e "Enable synchronization of user data:" +echo -e "\t$ snap set $SNAP_NAME LDAP_SYNC_USER_DATA='true'" +echo -e "\n" +echo -e "Ldap Sync User Data Fieldmap." +echo -e "A field map for the matching:" +echo -e "\t$ snap set $SNAP_NAME LDAP_SYNC_USER_DATA_FIELDMAP={\"cn\":\"name\", \"mail\":\"email\"}" +echo -e "\n" +echo -e "Ldap Sync Group Roles." +echo -e "\t$ snap set $SNAP_NAME LDAP_SYNC_GROUP_ROLES=''" +echo -e "\n" +echo -e "Ldap Default Domain." +echo -e "The default domain of the ldap it is used to create email if the field is not map correctly with the LDAP_SYNC_USER_DATA_FIELDMAP:" +echo -e "\t$ snap set $SNAP_NAME LDAP_DEFAULT_DOMAIN=''" +echo -e "\n" # parse config file for supported settings keys echo -e "wekan supports settings keys" echo -e "values can be changed by calling\n$ snap set $SNAP_NAME <key name>='<key value>'" diff --git a/snapcraft.yaml b/snapcraft.yaml index dbe738d0..9ad94fe5 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -65,7 +65,7 @@ apps: parts: mongodb: - source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.21.tgz + source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.0.3.tgz plugin: dump stage-packages: [libssl1.0.0] filesets: @@ -83,7 +83,6 @@ parts: plugin: nodejs node-engine: 8.12.0 node-packages: - - npm - node-gyp - node-pre-gyp - fibers@2.0.0 @@ -93,7 +92,6 @@ parts: - python - g++ - capnproto - - npm - curl - execstack stage-packages: @@ -122,9 +120,11 @@ parts: # Removed from build-packages: - paxctl #echo "Applying paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303" #paxctl -mC `which node` + #echo "Installing npm" + #curl -L https://www.npmjs.com/install.sh | sh echo "Installing meteor" curl https://install.meteor.com/ -o install_meteor.sh - sed -i "s|RELEASE=.*|RELEASE=\"1.6.0.1\"|g" install_meteor.sh + #sed -i "s|RELEASE=.*|RELEASE=\"1.8.1-beta.0\"|g" install_meteor.sh chmod +x install_meteor.sh sh install_meteor.sh rm install_meteor.sh @@ -142,6 +142,16 @@ parts: sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' meteor-useraccounts-core/package.js cd .. fi + if [ ! -d "packages/meteor-accounts-cas" ]; then + cd packages + git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git meteor-accounts-cas + cd .. + fi + if [ ! -d "packages/wekan-ldap" ]; then + cd packages + git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git + cd .. + fi rm -rf package-lock.json .build meteor add standard-minifier-js --allow-superuser meteor npm install --allow-superuser |