Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0834f6ed1e | ||
|
|
9d5e288bca | ||
|
|
0fe40ad9ec | ||
|
|
0765278195 | ||
|
|
f782b9c298 | ||
|
|
e611685e50 | ||
|
|
3edef95c4e | ||
|
|
9b902e1ca0 | ||
|
|
fc835815d8 | ||
|
|
da083c2e20 | ||
|
|
6ee17ab080 | ||
|
|
1e34585548 | ||
|
|
76cd5a2844 | ||
|
|
ffd1b93abf | ||
|
|
1eea102f26 | ||
|
|
94c1367684 | ||
|
|
9bf19f81a4 | ||
|
|
21de0ec396 | ||
|
|
ab4fec0f3c | ||
|
|
b983479476 | ||
|
|
f6e5e514ef | ||
|
|
ddd828083e | ||
|
|
e079b4df17 | ||
|
|
4e5ff17e18 | ||
|
|
86e1a9cd5d | ||
|
|
8a0efffdf8 | ||
|
|
c21f710a70 | ||
|
|
f3382abe3b | ||
|
|
d6c86f3cba | ||
|
|
3a6516f0cb | ||
|
|
b795115042 | ||
|
|
52fe845d24 | ||
|
|
b2b6bb2d39 | ||
|
|
db40ca25ac | ||
|
|
3eef42eee0 | ||
|
|
38d0ad74a9 | ||
|
|
3bbc805ee4 | ||
|
|
c37a454bd5 | ||
|
|
6f98be5608 | ||
|
|
ab4dab2ade | ||
|
|
88ffddfeba | ||
|
|
36f148a7cb | ||
|
|
fd1af07e43 | ||
|
|
fca87b53f0 | ||
|
|
ce0473480b | ||
|
|
828f6ea321 |
No files matched your search
+91
-1
@@ -1,3 +1,93 @@
|
||||
# v2.74 2019-05-14 Wekan release
|
||||
|
||||
This release fixes the following bugs:
|
||||
|
||||
- Add missing [wekan-ldap#40](https://github.com/wekan/wekan-ldap/pull/40) code about
|
||||
[LDAP_SYNC_ADMIN_STATUS](https://github.com/wekan/wekan/commit/0fe40ad9ec82ef2045578f4cc1e2ebb6cc80d47a).
|
||||
Thanks to JulianJacobi, n-st, chirrut2 and xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v2.73 2019-05-14 Wekan release
|
||||
|
||||
This release fixes the following bugs with Apache I-CLA:
|
||||
|
||||
- [Card count placement and export API functionality back](https://github.com/wekan/wekan/pulls/2406).
|
||||
Thanks to bentiss.
|
||||
- [Few fixes for Dockerfile](https://github.com/wekan/wekan/pulls/2407).
|
||||
Thanks to bentiss.
|
||||
|
||||
and fixes the following bugs:
|
||||
|
||||
- Fixed [#2338](https://github.com/wekan/wekan/issues/2338) -> [Slow opening of big boards with too many archived items](https://github.com/wekan/wekan/pull/2402).
|
||||
If some Wekan users see errors with this, please empty your browser cache.
|
||||
Thanks to nerminator.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v2.72 2019-05-13 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Added BIDI support to "Add Card"](https://github.com/wekan/wekan/pull/2401).
|
||||
Related [#884](https://github.com/wekan/wekan/issues/884).
|
||||
Thanks to guyzyl.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v2.71 2019-05-12 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Add partentId support on card web API](https://github.com/wekan/wekan/pulls/2400).
|
||||
Thanks to atilaromero.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v2.70 2019-05-11 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [View markdown on List names, Custom Fields (Text and Dropdown), Label names,
|
||||
All Boards view Board names and Board descriptions](https://github.com/wekan/wekan/commit/b795115042c2eb6bccbf029f21d78849a44128ca).
|
||||
Related [#2334](https://github.com/wekan/wekan/issues/2334).
|
||||
Thanks to shaygover and xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v2.69 2019-05-11 Wekan release
|
||||
|
||||
This release fixes the following translation names:
|
||||
|
||||
- [Fix translation name in Wekan menu: oc to Occitan](https://github.com/wekan/wekan/commit/db40ca25ac5df17fcc8b7c93f12b7e2bffc349d2).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v2.68 2019-05-10 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Option to login to the LDAP server with the user's own username and password, instead of an administrator
|
||||
key](https://github.com/wekan/wekan/pull/2399). Default: false (use administrator key).
|
||||
With new setting: LDAP_USER_AUTHENTICATION=true.
|
||||
Thanks to thiagofernando.
|
||||
- [Added above new LDAP_USER_AUTHENTION=true option to Snap, Docker and Source settings](https://github.com/wekan/wekan/commit/3bbc805ee42e3c1638b50260d3fafc2b5f936923).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v2.67 2019-05-10 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Move board to Archive button at each board at All Boards page](https://github.com/wekan/wekan/commit/828f6ea321020eda77fea399df52889e2081dfac).
|
||||
Thanks to xet7. Related [#2389](https://github.com/wekan/wekan/issues/2389).
|
||||
- [If adding Subtasks does not work on old board, added wiki page how to make it work again](https://github.com/wekan/wekan/wiki/Subtasks).
|
||||
Thanks to xet7.
|
||||
|
||||
Thanks to above GitHub users for their contributions and translators for their translations.
|
||||
|
||||
# v2.66 2019-05-09 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
@@ -89,7 +179,7 @@ and adds the following updates:
|
||||
and fixes the following bugs:
|
||||
|
||||
- [Fix Full width of lists and space before first list](https://github.com/wekan/wekan/pull/2343).
|
||||
Thanks to hupptehcnologies.
|
||||
Thanks to hupptechnologies.
|
||||
- Remove [extra](https://github.com/wekan/wekan/pull/2332) [quotes](https://github.com/wekan/wekan/pull/2333) from docker-compose.yml.
|
||||
Thanks to hibare.
|
||||
- Fix Docker builds by moving all separately cloned wekan/packages/* repos like ldap, oidc, etc code to wekan repo code,
|
||||
|
||||
+6
-5
@@ -61,6 +61,7 @@ ENV BUILD_DEPS="apt-utils bsdtar gnupg gosu wget curl bzip2 build-essential pyth
|
||||
LDAP_ENCRYPTION=false \
|
||||
LDAP_CA_CERT="" \
|
||||
LDAP_REJECT_UNAUTHORIZED=false \
|
||||
LDAP_USER_AUTHENTICATION=false \
|
||||
LDAP_USER_SEARCH_FILTER="" \
|
||||
LDAP_USER_SEARCH_SCOPE="" \
|
||||
LDAP_USER_SEARCH_FIELD="" \
|
||||
@@ -193,7 +194,7 @@ RUN \
|
||||
\
|
||||
# Get additional packages
|
||||
#mkdir -p /home/wekan/app/packages && \
|
||||
chown wekan:wekan --recursive /home/wekan && \
|
||||
#chown wekan:wekan --recursive /home/wekan && \
|
||||
# REPOS BELOW ARE INCLUDED TO WEKAN REPO
|
||||
#cd /home/wekan/app/packages && \
|
||||
#gosu wekan:wekan git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router && \
|
||||
@@ -211,7 +212,7 @@ RUN \
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor -- help; \
|
||||
\
|
||||
# extract the OpenAPI specification
|
||||
npm install -g api2html@0.3.0 && \
|
||||
npm install -g api2html@0.3.3 && \
|
||||
mkdir -p /home/wekan/python && \
|
||||
chown wekan:wekan --recursive /home/wekan/python && \
|
||||
cd /home/wekan/python && \
|
||||
@@ -219,9 +220,9 @@ RUN \
|
||||
cd /home/wekan/python/esprima-python && \
|
||||
python3 setup.py install --record files.txt && \
|
||||
cd /home/wekan/app &&\
|
||||
mkdir -p ./public/api && \
|
||||
python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml && \
|
||||
/opt/nodejs/bin/api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml; \
|
||||
gosu wekan:wekan mkdir -p ./public/api && \
|
||||
gosu wekan:wekan python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml && \
|
||||
gosu wekan:wekan /opt/nodejs/bin/api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml; \
|
||||
# Build app
|
||||
cd /home/wekan/app && \
|
||||
gosu wekan:wekan /home/wekan/.meteor/meteor add standard-minifier-js && \
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
|
||||
appVersion: "v2.66.0"
|
||||
appVersion: "v2.74.0"
|
||||
files:
|
||||
userUploads:
|
||||
- README.md
|
||||
|
||||
@@ -14,7 +14,7 @@ BlazeComponent.extendComponent({
|
||||
const currentBoardId = Session.get('currentBoard');
|
||||
if (!currentBoardId)
|
||||
return;
|
||||
const handle = subManager.subscribe('board', currentBoardId);
|
||||
const handle = subManager.subscribe('board', currentBoardId, false);
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
this.isBoardReady.set(handle.ready());
|
||||
|
||||
@@ -18,11 +18,15 @@ template(name="boardList")
|
||||
else
|
||||
a.js-open-board.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}")
|
||||
span.details
|
||||
span.board-list-item-name= title
|
||||
span.board-list-item-name
|
||||
+viewer
|
||||
= title
|
||||
i.fa.js-star-board(
|
||||
class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}"
|
||||
title="{{_ 'star-board-title'}}")
|
||||
p.board-list-item-desc= description
|
||||
p.board-list-item-desc
|
||||
+viewer
|
||||
= description
|
||||
if hasSpentTimeCards
|
||||
i.fa.js-has-spenttime-cards(
|
||||
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
|
||||
@@ -30,8 +34,9 @@ template(name="boardList")
|
||||
i.fa.js-clone-board(
|
||||
class="fa-clone"
|
||||
title="{{_ 'duplicate-board'}}")
|
||||
|
||||
|
||||
i.fa.js-archive-board(
|
||||
class="fa-archive"
|
||||
title="{{_ 'archive-board'}}")
|
||||
|
||||
template(name="boardListHeaderBar")
|
||||
h1 {{_ 'my-boards'}}
|
||||
|
||||
@@ -33,12 +33,12 @@ BlazeComponent.extendComponent({
|
||||
},
|
||||
|
||||
hasOvertimeCards() {
|
||||
subManager.subscribe('board', this.currentData()._id);
|
||||
subManager.subscribe('board', this.currentData()._id, false);
|
||||
return this.currentData().hasOvertimeCards();
|
||||
},
|
||||
|
||||
hasSpentTimeCards() {
|
||||
subManager.subscribe('board', this.currentData()._id);
|
||||
subManager.subscribe('board', this.currentData()._id, false);
|
||||
return this.currentData().hasSpentTimeCards();
|
||||
},
|
||||
|
||||
@@ -70,6 +70,11 @@ BlazeComponent.extendComponent({
|
||||
);
|
||||
evt.preventDefault();
|
||||
},
|
||||
'click .js-archive-board'(evt) {
|
||||
const boardId = this.currentData()._id;
|
||||
Meteor.call('archiveBoard', boardId);
|
||||
evt.preventDefault();
|
||||
},
|
||||
'click .js-accept-invite'() {
|
||||
const boardId = this.currentData()._id;
|
||||
Meteor.user().removeInvite(boardId);
|
||||
|
||||
@@ -106,15 +106,29 @@ $spaceBetweenTiles = 16px
|
||||
transition-duration: .15s
|
||||
transition-property: color, font-size, background
|
||||
|
||||
.fa-archive
|
||||
position: absolute;
|
||||
bottom: 0
|
||||
font-size: 14px
|
||||
height: 18px
|
||||
line-height: 18px
|
||||
opacity: 0
|
||||
left: 0
|
||||
padding: 9px 9px
|
||||
transition-duration: .15s
|
||||
transition-property: color, font-size, background
|
||||
|
||||
li:hover a
|
||||
&:hover
|
||||
.fa-star,
|
||||
.fa-clone,
|
||||
.fa-archive,
|
||||
.fa-star-o
|
||||
color: white
|
||||
|
||||
.fa-star,
|
||||
.fa-clone,
|
||||
.fa-archive,
|
||||
.fa-star-o
|
||||
color: white
|
||||
opacity: .75
|
||||
|
||||
@@ -4,9 +4,10 @@ template(name="cardCustomFieldsPopup")
|
||||
li.item(class="")
|
||||
a.name.js-select-field(href="#")
|
||||
span.full-name
|
||||
+viewer
|
||||
= name
|
||||
if hasCustomField
|
||||
i.fa.fa-check
|
||||
i.fa.fa-check
|
||||
hr
|
||||
a.quiet-button.full.js-settings
|
||||
i.fa.fa-cog
|
||||
@@ -61,9 +62,13 @@ template(name="cardCustomField-dropdown")
|
||||
select.inline
|
||||
each items
|
||||
if($eq data.value this._id)
|
||||
option(value=_id selected="selected") {{name}}
|
||||
option(value=_id selected="selected")
|
||||
+viewer
|
||||
= name
|
||||
else
|
||||
option(value=_id) {{name}}
|
||||
option(value=_id)
|
||||
+viewer
|
||||
= name
|
||||
.edit-controls.clearfix
|
||||
button.primary(type="submit") {{_ 'save'}}
|
||||
a.fa.fa-times-thin.js-close-inlined-form
|
||||
@@ -73,4 +78,4 @@ template(name="cardCustomField-dropdown")
|
||||
+viewer
|
||||
= selectedItem
|
||||
else
|
||||
| {{_ 'edit'}}
|
||||
| {{_ 'edit'}}
|
||||
@@ -77,7 +77,9 @@ template(name="cardDetails")
|
||||
h3.card-details-item-title {{_ 'labels'}}
|
||||
a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
|
||||
each labels
|
||||
span.card-label(class="card-label-{{color}}" title=name)= name
|
||||
span.card-label(class="card-label-{{color}}" title=name)
|
||||
+viewer
|
||||
= name
|
||||
if canModifyCard
|
||||
a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
|
||||
i.fa.fa-plus
|
||||
@@ -86,7 +88,8 @@ template(name="cardDetails")
|
||||
each customFieldsWD
|
||||
.card-details-item.card-details-item-customfield
|
||||
h3.card-details-item-title
|
||||
= definition.name
|
||||
+viewer
|
||||
= definition.name
|
||||
+cardCustomField
|
||||
|
||||
.card-details-items
|
||||
|
||||
@@ -424,7 +424,7 @@ Template.moveCardPopup.events({
|
||||
});
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
subManager.subscribe('board', Session.get('currentBoard'));
|
||||
subManager.subscribe('board', Session.get('currentBoard'), false);
|
||||
this.selectedBoardId = new ReactiveVar(Session.get('currentBoard'));
|
||||
},
|
||||
|
||||
@@ -453,7 +453,7 @@ BlazeComponent.extendComponent({
|
||||
return [{
|
||||
'change .js-select-boards'(evt) {
|
||||
this.selectedBoardId.set($(evt.currentTarget).val());
|
||||
subManager.subscribe('board', this.selectedBoardId.get());
|
||||
subManager.subscribe('board', this.selectedBoardId.get(), false);
|
||||
},
|
||||
}];
|
||||
},
|
||||
@@ -676,7 +676,7 @@ BlazeComponent.extendComponent({
|
||||
if (selection === 'none') {
|
||||
this.parentBoard.set(null);
|
||||
} else {
|
||||
subManager.subscribe('board', $(evt.currentTarget).val());
|
||||
subManager.subscribe('board', $(evt.currentTarget).val(), false);
|
||||
this.parentBoard.set(selection);
|
||||
list.prop('disabled', false);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ template(name="cardLabelsPopup")
|
||||
a.card-label-edit-button.fa.fa-pencil.js-edit-label
|
||||
span.card-label.card-label-selectable.js-select-label(class="card-label-{{color}}"
|
||||
class="{{# if isLabelSelected ../_id }}active{{/if}}")
|
||||
= name
|
||||
+viewer
|
||||
= name
|
||||
if(isLabelSelected ../_id)
|
||||
i.card-label-selectable-icon.fa.fa-check
|
||||
if currentUser.isBoardAdmin
|
||||
|
||||
@@ -58,7 +58,8 @@ template(name="minicard")
|
||||
.minicard-custom-field
|
||||
if definition.showLabelOnMiniCard
|
||||
.minicard-custom-field-item
|
||||
= definition.name
|
||||
+viewer
|
||||
= definition.name
|
||||
.minicard-custom-field-item
|
||||
+viewer
|
||||
= trueValue
|
||||
|
||||
@@ -99,6 +99,10 @@
|
||||
.highlight
|
||||
color: #ce1414
|
||||
|
||||
.cardCount
|
||||
color: #8c8c8c
|
||||
font-size: 0.8em
|
||||
|
||||
.list-body
|
||||
flex: 1 1 auto
|
||||
flex-direction: column
|
||||
|
||||
@@ -39,7 +39,7 @@ template(name="addCardForm")
|
||||
.minicard-labels
|
||||
each getLabels
|
||||
.minicard-label(class="card-label-{{color}}" title="{{name}}")
|
||||
textarea.minicard-composer-textarea.js-card-title(autofocus)
|
||||
textarea.minicard-composer-textarea.js-card-title(autofocus dir="auto")
|
||||
if members.get
|
||||
.minicard-members.js-minicard-composer-members
|
||||
each members.get
|
||||
@@ -96,7 +96,7 @@ template(name="searchElementPopup")
|
||||
form
|
||||
label
|
||||
| {{_ 'title'}}
|
||||
input.js-element-title(type="text" placeholder="{{_ 'title'}}" autofocus required)
|
||||
input.js-element-title(type="text" placeholder="{{_ 'title'}}" autofocus required dir="auto")
|
||||
unless isTemplateSearch
|
||||
label {{_ 'boards'}}:
|
||||
.link-board-wrapper
|
||||
|
||||
@@ -348,7 +348,7 @@ BlazeComponent.extendComponent({
|
||||
|
||||
this.boardId = Session.get('currentBoard');
|
||||
// In order to get current board info
|
||||
subManager.subscribe('board', this.boardId);
|
||||
subManager.subscribe('board', this.boardId, false);
|
||||
this.board = Boards.findOne(this.boardId);
|
||||
// List where to insert card
|
||||
const list = $(Popup._getTopStack().openerElement).closest('.js-list');
|
||||
@@ -414,7 +414,7 @@ BlazeComponent.extendComponent({
|
||||
events() {
|
||||
return [{
|
||||
'change .js-select-boards'(evt) {
|
||||
subManager.subscribe('board', $(evt.currentTarget).val());
|
||||
subManager.subscribe('board', $(evt.currentTarget).val(), false);
|
||||
this.selectedBoardId.set($(evt.currentTarget).val());
|
||||
},
|
||||
'change .js-select-swimlanes'(evt) {
|
||||
@@ -500,13 +500,13 @@ BlazeComponent.extendComponent({
|
||||
}
|
||||
const boardId = board._id;
|
||||
// Subscribe to this board
|
||||
subManager.subscribe('board', boardId);
|
||||
subManager.subscribe('board', boardId, false);
|
||||
this.selectedBoardId = new ReactiveVar(boardId);
|
||||
|
||||
if (!this.isBoardTemplateSearch) {
|
||||
this.boardId = Session.get('currentBoard');
|
||||
// In order to get current board info
|
||||
subManager.subscribe('board', this.boardId);
|
||||
subManager.subscribe('board', this.boardId, false);
|
||||
this.swimlaneId = '';
|
||||
// Swimlane where to insert card
|
||||
const swimlane = $(Popup._getTopStack().openerElement).parents('.js-swimlane');
|
||||
@@ -547,7 +547,7 @@ BlazeComponent.extendComponent({
|
||||
} else if (this.isBoardTemplateSearch) {
|
||||
const boards = board.searchBoards(this.term.get());
|
||||
boards.forEach((board) => {
|
||||
subManager.subscribe('board', board.linkedId);
|
||||
subManager.subscribe('board', board.linkedId, false);
|
||||
});
|
||||
return boards;
|
||||
} else {
|
||||
@@ -558,7 +558,7 @@ BlazeComponent.extendComponent({
|
||||
events() {
|
||||
return [{
|
||||
'change .js-select-boards'(evt) {
|
||||
subManager.subscribe('board', $(evt.currentTarget).val());
|
||||
subManager.subscribe('board', $(evt.currentTarget).val(), false);
|
||||
this.selectedBoardId.set($(evt.currentTarget).val());
|
||||
},
|
||||
'submit .js-search-term-form'(evt) {
|
||||
|
||||
@@ -10,7 +10,8 @@ template(name="listHeader")
|
||||
a.list-header-left-icon.fa.fa-angle-left.js-unselect-list
|
||||
h2.list-header-name(
|
||||
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
|
||||
= title
|
||||
+viewer
|
||||
= title
|
||||
if wipLimit.enabled
|
||||
| (
|
||||
span(class="{{#if reachedWipLimit}}highlight{{/if}}") {{cards.count}}
|
||||
@@ -18,7 +19,7 @@ template(name="listHeader")
|
||||
|
||||
if showCardsCountForList cards.count
|
||||
|
|
||||
p.quiet.small {{cardsCount}} {{_ 'cards-count'}}
|
||||
span(class="cardCount") {{cardsCount}} {{_ 'cards-count'}}
|
||||
if isMiniScreen
|
||||
if currentList
|
||||
if isWatching
|
||||
|
||||
@@ -68,6 +68,8 @@ Template.userFormsLayout.helpers({
|
||||
name = 'Brezhoneg';
|
||||
} else if (lang.name === 'ig') {
|
||||
name = 'Igbo';
|
||||
} else if (lang.name === 'oc') {
|
||||
name = 'Occitan';
|
||||
}
|
||||
return { tag, name };
|
||||
}).sort(function(a, b) {
|
||||
|
||||
@@ -189,7 +189,9 @@ template(name="labelsWidget")
|
||||
each currentBoard.labels
|
||||
a.card-label(class="card-label-{{color}}"
|
||||
class="{{#if currentUser.isNotCommentOnly}}js-label{{/if}}")
|
||||
span.card-label-name= name
|
||||
span.card-label-name
|
||||
+viewer
|
||||
= name
|
||||
if currentUser.isBoardAdmin
|
||||
a.card-label.add-label.js-add-label
|
||||
i.fa.fa-plus
|
||||
|
||||
@@ -1,54 +1,56 @@
|
||||
template(name="archivesSidebar")
|
||||
+basicTabs(tabs=tabs)
|
||||
|
||||
+tabContent(slug="cards")
|
||||
p.quiet
|
||||
a.js-restore-all-cards {{_ 'restore-all'}}
|
||||
| -
|
||||
a.js-delete-all-cards {{_ 'delete-all'}}
|
||||
each archivedCards
|
||||
.minicard-wrapper.js-minicard
|
||||
+minicard(this)
|
||||
if currentUser.isBoardMember
|
||||
if isArchiveReady.get
|
||||
+basicTabs(tabs=tabs)
|
||||
+tabContent(slug="cards")
|
||||
p.quiet
|
||||
a.js-restore-card {{_ 'restore'}}
|
||||
a.js-restore-all-cards {{_ 'restore-all'}}
|
||||
| -
|
||||
a.js-delete-card {{_ 'delete'}}
|
||||
if cardIsInArchivedList
|
||||
p.quiet.small ({{_ 'warn-list-archived'}})
|
||||
else
|
||||
p.no-items-message {{_ 'no-archived-cards'}}
|
||||
|
||||
+tabContent(slug="lists")
|
||||
p.quiet
|
||||
a.js-restore-all-lists {{_ 'restore-all'}}
|
||||
| -
|
||||
a.js-delete-all-lists {{_ 'delete-all'}}
|
||||
ul.archived-lists
|
||||
each archivedLists
|
||||
li.archived-lists-item
|
||||
= title
|
||||
a.js-delete-all-cards {{_ 'delete-all'}}
|
||||
each archivedCards
|
||||
.minicard-wrapper.js-minicard
|
||||
+minicard(this)
|
||||
if currentUser.isBoardMember
|
||||
p.quiet
|
||||
a.js-restore-list {{_ 'restore'}}
|
||||
a.js-restore-card {{_ 'restore'}}
|
||||
| -
|
||||
a.js-delete-list {{_ 'delete'}}
|
||||
else
|
||||
li.no-items-message {{_ 'no-archived-lists'}}
|
||||
a.js-delete-card {{_ 'delete'}}
|
||||
if cardIsInArchivedList
|
||||
p.quiet.small ({{_ 'warn-list-archived'}})
|
||||
else
|
||||
p.no-items-message {{_ 'no-archived-cards'}}
|
||||
|
||||
+tabContent(slug="swimlanes")
|
||||
p.quiet
|
||||
a.js-restore-all-swimlanes {{_ 'restore-all'}}
|
||||
| -
|
||||
a.js-delete-all-swimlanes {{_ 'delete-all'}}
|
||||
ul.archived-lists
|
||||
each archivedSwimlanes
|
||||
li.archived-lists-item
|
||||
= title
|
||||
if currentUser.isBoardMember
|
||||
p.quiet
|
||||
a.js-restore-swimlane {{_ 'restore'}}
|
||||
| -
|
||||
a.js-delete-swimlane {{_ 'delete'}}
|
||||
else
|
||||
li.no-items-message {{_ 'no-archived-swimlanes'}}
|
||||
+tabContent(slug="lists")
|
||||
p.quiet
|
||||
a.js-restore-all-lists {{_ 'restore-all'}}
|
||||
| -
|
||||
a.js-delete-all-lists {{_ 'delete-all'}}
|
||||
ul.archived-lists
|
||||
each archivedLists
|
||||
li.archived-lists-item
|
||||
= title
|
||||
if currentUser.isBoardMember
|
||||
p.quiet
|
||||
a.js-restore-list {{_ 'restore'}}
|
||||
| -
|
||||
a.js-delete-list {{_ 'delete'}}
|
||||
else
|
||||
li.no-items-message {{_ 'no-archived-lists'}}
|
||||
|
||||
+tabContent(slug="swimlanes")
|
||||
p.quiet
|
||||
a.js-restore-all-swimlanes {{_ 'restore-all'}}
|
||||
| -
|
||||
a.js-delete-all-swimlanes {{_ 'delete-all'}}
|
||||
ul.archived-lists
|
||||
each archivedSwimlanes
|
||||
li.archived-lists-item
|
||||
= title
|
||||
if currentUser.isBoardMember
|
||||
p.quiet
|
||||
a.js-restore-swimlane {{_ 'restore'}}
|
||||
| -
|
||||
a.js-delete-swimlane {{_ 'delete'}}
|
||||
else
|
||||
li.no-items-message {{_ 'no-archived-swimlanes'}}
|
||||
else
|
||||
+spinner
|
||||
@@ -1,4 +1,26 @@
|
||||
const subManager = new SubsManager();
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
onCreated() {
|
||||
this.isArchiveReady = new ReactiveVar(false);
|
||||
|
||||
// The pattern we use to manually handle data loading is described here:
|
||||
// https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management/using-subs-manager
|
||||
// XXX The boardId should be readed from some sort the component "props",
|
||||
// unfortunatly, Blaze doesn't have this notion.
|
||||
this.autorun(() => {
|
||||
const currentBoardId = Session.get('currentBoard');
|
||||
if (!currentBoardId)
|
||||
return;
|
||||
const handle = subManager.subscribe('board', currentBoardId, true);
|
||||
Tracker.nonreactive(() => {
|
||||
Tracker.autorun(() => {
|
||||
this.isArchiveReady.set( handle.ready() );
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
tabs() {
|
||||
return [
|
||||
{ name: TAPi18n.__('cards'), slug: 'cards' },
|
||||
|
||||
@@ -6,7 +6,9 @@ template(name="customFieldsSidebar")
|
||||
div.minicard
|
||||
a.fa.fa-pencil.js-edit-custom-field.minicard-edit-button
|
||||
div.minicard-title
|
||||
| {{ name }} ({{ type }})
|
||||
+viewer
|
||||
=name
|
||||
| ({{ type }})
|
||||
|
||||
if currentUser.isBoardMember
|
||||
hr
|
||||
|
||||
@@ -119,6 +119,8 @@ Template.changeLanguagePopup.helpers({
|
||||
name = 'Brezhoneg';
|
||||
} else if (lang.name === 'ig') {
|
||||
name = 'Igbo';
|
||||
} else if (lang.name === 'oc') {
|
||||
name = 'Occitan';
|
||||
}
|
||||
return { tag, name };
|
||||
}).sort(function (a, b) {
|
||||
|
||||
@@ -400,6 +400,9 @@ services:
|
||||
# Reject Unauthorized Certificate
|
||||
#- LDAP_REJECT_UNAUTHORIZED=false
|
||||
#
|
||||
# Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key).
|
||||
#- LDAP_USER_AUTHENTICATION="true"
|
||||
#
|
||||
# Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
|
||||
#- LDAP_USER_SEARCH_FILTER=
|
||||
#
|
||||
|
||||
+30
-30
@@ -23,10 +23,10 @@
|
||||
"act-createCustomField": "creó el campo personalizado __customField__ en la tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__",
|
||||
"act-createList": "añadida la lista __list__ al tablero __board__",
|
||||
"act-addBoardMember": "añadido el mimbro __member__ al tablero __board__",
|
||||
"act-archivedBoard": "El tablero __board__ se ha movido a Archivo",
|
||||
"act-archivedCard": "La tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__ se ha movido a Archivo",
|
||||
"act-archivedList": "La lista __list__ del carril __swimlane__ del tablero __board__ se ha movido a Archivo",
|
||||
"act-archivedSwimlane": "El carril __swimlane__ del tablero __board__ se ha movido a Archivo",
|
||||
"act-archivedBoard": "El tablero __board__ se ha archivado",
|
||||
"act-archivedCard": "La tarjeta __card__ de la lista __list__ del carril __swimlane__ del tablero __board__ se ha archivado",
|
||||
"act-archivedList": "La lista __list__ del carril __swimlane__ del tablero __board__ se ha archivado",
|
||||
"act-archivedSwimlane": "El carril __swimlane__ del tablero __board__ se ha archivado",
|
||||
"act-importBoard": "importado el tablero __board__",
|
||||
"act-importCard": "importada la tarjeta __card__ a la lista __list__ del carrril __swimlane__ del tablero __board__",
|
||||
"act-importList": "importada la lista __list__ al carril __swimlane__ del tablero __board__",
|
||||
@@ -42,7 +42,7 @@
|
||||
"activities": "Actividades",
|
||||
"activity": "Actividad",
|
||||
"activity-added": "ha añadido %s a %s",
|
||||
"activity-archived": "%s movido a Archivo",
|
||||
"activity-archived": "%s se ha archivado",
|
||||
"activity-attached": "ha adjuntado %s a %s",
|
||||
"activity-created": "ha creado %s",
|
||||
"activity-customfield-created": "creó el campo personalizado %s",
|
||||
@@ -92,19 +92,19 @@
|
||||
"and-n-other-card_plural": "y otras __count__ tarjetas",
|
||||
"apply": "Aplicar",
|
||||
"app-is-offline": "Cargando, espera por favor. Refrescar esta página causará pérdida de datos. Si la carga no funciona, por favor comprueba que el servidor no se ha parado.",
|
||||
"archive": "Mover al Archivo",
|
||||
"archive-all": "Mover todo al Archivo",
|
||||
"archive-board": "Mover Tablero al Archivo",
|
||||
"archive-card": "Mover tarjeta al Archivo",
|
||||
"archive-list": "Mover Lista al Archivo",
|
||||
"archive-swimlane": "Mover carril al Archivo",
|
||||
"archive-selection": "Mover selección al Archivo",
|
||||
"archiveBoardPopup-title": "¿Mover Tablero al Archivo?",
|
||||
"archived-items": "Archivar",
|
||||
"archive": "Archivar",
|
||||
"archive-all": "Archivar todo",
|
||||
"archive-board": "Archivar este tablero",
|
||||
"archive-card": "Archivar esta tarjeta",
|
||||
"archive-list": "Archivar esta lista",
|
||||
"archive-swimlane": "Archivar este carril",
|
||||
"archive-selection": "Archivar esta selección",
|
||||
"archiveBoardPopup-title": "¿Archivar este tablero?",
|
||||
"archived-items": "Archivo",
|
||||
"archived-boards": "Tableros en Archivo",
|
||||
"restore-board": "Restaurar el tablero",
|
||||
"no-archived-boards": "No hay Tableros en el Archivo",
|
||||
"archives": "Archivar",
|
||||
"archives": "Archivo",
|
||||
"template": "Plantilla",
|
||||
"templates": "Plantillas",
|
||||
"assign-member": "Asignar miembros",
|
||||
@@ -133,8 +133,8 @@
|
||||
"board-view-lists": "Listas",
|
||||
"bucket-example": "Como “Cosas por hacer” por ejemplo",
|
||||
"cancel": "Cancelar",
|
||||
"card-archived": "Esta tarjeta se movió al Archivo",
|
||||
"board-archived": "Este tablero se movió al Archivo",
|
||||
"card-archived": "Se archivó esta tarjeta",
|
||||
"board-archived": "Se archivó este tablero",
|
||||
"card-comments-title": "Esta tarjeta tiene %s comentarios.",
|
||||
"card-delete-notice": "la eliminación es permanente. Perderás todas las acciones asociadas a esta tarjeta.",
|
||||
"card-delete-pop": "Se eliminarán todas las acciones del historial de actividades y no se podrá volver a abrir la tarjeta. Esta acción no puede deshacerse.",
|
||||
@@ -347,7 +347,7 @@
|
||||
"leave-board-pop": "¿Seguro que quieres abandonar __boardTitle__? Serás desvinculado de todas las tarjetas en este tablero.",
|
||||
"leaveBoardPopup-title": "¿Abandonar el tablero?",
|
||||
"link-card": "Enlazar a esta tarjeta",
|
||||
"list-archive-cards": "Mover todas las tarjetas de la lista al Archivo",
|
||||
"list-archive-cards": "Archivar todas las tarjetas de esta lista",
|
||||
"list-archive-cards-pop": "Esto eliminará del tablero todas las tarjetas en esta lista. Para ver las tarjetas en el Archivo y recuperarlas al tablero haga click en \"Menu\" > \"Archivo\"",
|
||||
"list-move-cards": "Mover todas las tarjetas de esta lista",
|
||||
"list-select-cards": "Seleccionar todas las tarjetas de esta lista",
|
||||
@@ -514,7 +514,7 @@
|
||||
"OS_Loadavg": "Carga media del sistema",
|
||||
"OS_Platform": "Plataforma del sistema",
|
||||
"OS_Release": "Publicación del sistema",
|
||||
"OS_Totalmem": "Memoria Total del sistema",
|
||||
"OS_Totalmem": "Memoria total del sistema",
|
||||
"OS_Type": "Tipo de sistema",
|
||||
"OS_Uptime": "Tiempo activo del sistema",
|
||||
"days": "días",
|
||||
@@ -528,8 +528,8 @@
|
||||
"no": "No",
|
||||
"accounts": "Cuentas",
|
||||
"accounts-allowEmailChange": "Permitir cambiar el correo electrónico",
|
||||
"accounts-allowUserNameChange": "Permitir el cambio del nombre de usuario",
|
||||
"createdAt": "Creado en",
|
||||
"accounts-allowUserNameChange": "Permitir cambiar el nombre de usuario",
|
||||
"createdAt": "Fecha de alta",
|
||||
"verified": "Verificado",
|
||||
"active": "Activo",
|
||||
"card-received": "Recibido",
|
||||
@@ -592,7 +592,7 @@
|
||||
"set-filter": "Filtrar",
|
||||
"r-moved-to": "Movido a",
|
||||
"r-moved-from": "Movido desde",
|
||||
"r-archived": "Movido a Archivo",
|
||||
"r-archived": "Se archivó",
|
||||
"r-unarchived": "Restaurado del archivo",
|
||||
"r-a-card": "una tarjeta",
|
||||
"r-when-a-label-is": "Cuando una etiqueta es",
|
||||
@@ -610,11 +610,11 @@
|
||||
"r-when-the-item": "Cuando el elemento de la lista de verificación es",
|
||||
"r-checked": "Marcado",
|
||||
"r-unchecked": "Desmarcado",
|
||||
"r-move-card-to": "Mover tarjeta a",
|
||||
"r-move-card-to": "Mover la tarjeta a",
|
||||
"r-top-of": "Arriba de",
|
||||
"r-bottom-of": "Abajo de",
|
||||
"r-its-list": "su lista",
|
||||
"r-archive": "Mover al Archivo",
|
||||
"r-archive": "Archivar",
|
||||
"r-unarchive": "Restaurar del Archivo",
|
||||
"r-card": "tarjeta",
|
||||
"r-add": "Añadir",
|
||||
@@ -635,15 +635,15 @@
|
||||
"r-to": "a",
|
||||
"r-subject": "asunto",
|
||||
"r-rule-details": "Detalle de la regla",
|
||||
"r-d-move-to-top-gen": "Mover tarjeta arriba de su lista",
|
||||
"r-d-move-to-top-spec": "Mover tarjeta arriba de la lista",
|
||||
"r-d-move-to-bottom-gen": "Mover tarjeta abajo de su lista",
|
||||
"r-d-move-to-bottom-spec": "Mover tarjeta al fondo de la lista",
|
||||
"r-d-move-to-top-gen": "Mover la tarjeta al inicio de su lista",
|
||||
"r-d-move-to-top-spec": "Mover la tarjeta al inicio de la lista",
|
||||
"r-d-move-to-bottom-gen": "Mover la tarjeta al final de su lista",
|
||||
"r-d-move-to-bottom-spec": "Mover la tarjeta al final de la lista",
|
||||
"r-d-send-email": "Enviar email",
|
||||
"r-d-send-email-to": "a",
|
||||
"r-d-send-email-subject": "asunto",
|
||||
"r-d-send-email-message": "mensaje",
|
||||
"r-d-archive": "Mover tarjeta al Archivo",
|
||||
"r-d-archive": "Archivar la tarjeta",
|
||||
"r-d-unarchive": "Restaurar tarjeta del Archivo",
|
||||
"r-d-add-label": "Añadir etiqueta",
|
||||
"r-d-remove-label": "Eliminar etiqueta",
|
||||
@@ -675,7 +675,7 @@
|
||||
"authentication-method": "Método de autenticación",
|
||||
"authentication-type": "Tipo de autenticación",
|
||||
"custom-product-name": "Nombre de producto personalizado",
|
||||
"layout": "Disñeo",
|
||||
"layout": "Diseño",
|
||||
"hide-logo": "Ocultar logo",
|
||||
"add-custom-html-after-body-start": "Añade HTML personalizado después de <body> ",
|
||||
"add-custom-html-before-body-end": "Añade HTML personalizado después de </body>",
|
||||
|
||||
+70
-70
@@ -83,7 +83,7 @@
|
||||
"added": "Lisätty",
|
||||
"addMemberPopup-title": "Jäsenet",
|
||||
"admin": "Ylläpitäjä",
|
||||
"admin-desc": "Voi nähfä ja muokata kortteja, poistaa jäseniä, ja muuttaa taulun asetuksia.",
|
||||
"admin-desc": "Voi nähdä ja muokata kortteja, poistaa jäseniä, ja muuttaa taulun asetuksia.",
|
||||
"admin-announcement": "Ilmoitus",
|
||||
"admin-announcement-active": "Aktiivinen järjestelmänlaajuinen ilmoitus",
|
||||
"admin-announcement-title": "Ilmoitus ylläpitäjältä",
|
||||
@@ -103,7 +103,7 @@
|
||||
"archived-items": "Arkisto",
|
||||
"archived-boards": "Taulut Arkistossa",
|
||||
"restore-board": "Palauta taulu",
|
||||
"no-archived-boards": "Ei tauluja Arkistossa",
|
||||
"no-archived-boards": "Ei tauluja Arkistossa.",
|
||||
"archives": "Arkisto",
|
||||
"template": "Malli",
|
||||
"templates": "Mallit",
|
||||
@@ -114,7 +114,7 @@
|
||||
"attachmentDeletePopup-title": "Poista liitetiedosto?",
|
||||
"attachments": "Liitetiedostot",
|
||||
"auto-watch": "Automaattisesti seuraa tauluja kun ne on luotu",
|
||||
"avatar-too-big": "Profiilikuva on liian suuri (70KB maksimi)",
|
||||
"avatar-too-big": "Profiilikuva on liian suuri (enintään 70 kt)",
|
||||
"back": "Takaisin",
|
||||
"board-change-color": "Muokkaa väriä",
|
||||
"board-nb-stars": "%s tähteä",
|
||||
@@ -125,9 +125,9 @@
|
||||
"boardChangeTitlePopup-title": "Nimeä taulu uudelleen",
|
||||
"boardChangeVisibilityPopup-title": "Muokkaa näkyvyyttä",
|
||||
"boardChangeWatchPopup-title": "Muokkaa seuraamista",
|
||||
"boardMenuPopup-title": "Taulu asetukset",
|
||||
"boardMenuPopup-title": "Tauluasetukset",
|
||||
"boards": "Taulut",
|
||||
"board-view": "Taulu näkymä",
|
||||
"board-view": "Taulunäkymä",
|
||||
"board-view-cal": "Kalenteri",
|
||||
"board-view-swimlanes": "Swimlanet",
|
||||
"board-view-lists": "Listat",
|
||||
@@ -136,8 +136,8 @@
|
||||
"card-archived": "Tämä kortti on siirretty Arkistoon.",
|
||||
"board-archived": "Tämä taulu on siirretty Arkistoon.",
|
||||
"card-comments-title": "Tässä kortissa on %s kommenttia.",
|
||||
"card-delete-notice": "Poistaminen on lopullista. Menetät kaikki toimet jotka on liitetty tähän korttiin.",
|
||||
"card-delete-pop": "Kaikki toimet poistetaan toimintasyötteestä ja et tule pystymään uudelleenavaamaan korttia. Tätä ei voi peruuttaa.",
|
||||
"card-delete-notice": "Poistaminen on lopullista. Menetät kaikki tähän korttiin liitetyt toimet.",
|
||||
"card-delete-pop": "Kaikki toimet poistetaan toimintasyötteestä, etkä pysty avata korttia uudelleen. Tätä ei voi peruuttaa.",
|
||||
"card-delete-suggest-archive": "Voit siirtää kortin Arkistoon poistaaksesi sen taululta ja säilyttääksesi toimintalokin.",
|
||||
"card-due": "Erääntyy",
|
||||
"card-due-on": "Erääntyy",
|
||||
@@ -154,14 +154,14 @@
|
||||
"cardCustomField-datePopup-title": "Muokkaa päivää",
|
||||
"cardCustomFieldsPopup-title": "Muokkaa mukautettuja kenttiä",
|
||||
"cardDeletePopup-title": "Poista kortti?",
|
||||
"cardDetailsActionsPopup-title": "Kortti toimet",
|
||||
"cardDetailsActionsPopup-title": "Korttitoimet",
|
||||
"cardLabelsPopup-title": "Tunnisteet",
|
||||
"cardMembersPopup-title": "Jäsenet",
|
||||
"cardMorePopup-title": "Lisää",
|
||||
"cardTemplatePopup-title": "Luo malli",
|
||||
"cards": "Kortit",
|
||||
"cards-count": "korttia",
|
||||
"casSignIn": "CAS kirjautuminen",
|
||||
"casSignIn": "CAS-kirjautuminen",
|
||||
"cardType-card": "Kortti",
|
||||
"cardType-linkedCard": "Linkitetty kortti",
|
||||
"cardType-linkedBoard": "Linkitetty taulu",
|
||||
@@ -182,7 +182,7 @@
|
||||
"clipboard": "Leikepöytä tai raahaa ja pudota",
|
||||
"close": "Sulje",
|
||||
"close-board": "Sulje taulu",
|
||||
"close-board-pop": "Voit palauttaa taulun klikkaamalla “Arkisto” painiketta taululistan yläpalkista.",
|
||||
"close-board-pop": "Voit palauttaa taulun klikkaamalla “Arkisto”-painiketta taululistan yläpalkista.",
|
||||
"color-black": "musta",
|
||||
"color-blue": "sininen",
|
||||
"color-crimson": "karmiininpunainen",
|
||||
@@ -216,14 +216,14 @@
|
||||
"no-comments": "Ei kommentteja",
|
||||
"no-comments-desc": "Ei voi nähdä kommentteja ja toimintaa.",
|
||||
"computer": "Tietokone",
|
||||
"confirm-subtask-delete-dialog": "Oletko varma että haluat poistaa alitehtävän?",
|
||||
"confirm-checklist-delete-dialog": "Oletko varma että haluat poistaa tarkistuslistan?",
|
||||
"confirm-subtask-delete-dialog": "Haluatko varmasti poistaa alitehtävän?",
|
||||
"confirm-checklist-delete-dialog": "Haluatko varmasti poistaa tarkistuslistan?",
|
||||
"copy-card-link-to-clipboard": "Kopioi kortin linkki leikepöydälle",
|
||||
"linkCardPopup-title": "Linkitä kortti",
|
||||
"searchElementPopup-title": "Etsi",
|
||||
"copyCardPopup-title": "Kopioi kortti",
|
||||
"copyChecklistToManyCardsPopup-title": "Kopioi tarkistuslistan malli monille korteille",
|
||||
"copyChecklistToManyCardsPopup-instructions": "Kohde korttien otsikot ja kuvaukset JSON muodossa",
|
||||
"copyChecklistToManyCardsPopup-instructions": "Kohde korttien otsikot ja kuvaukset JSON-muodossa",
|
||||
"copyChecklistToManyCardsPopup-format": "[ {\"title\": \"Ensimmäisen kortin otsikko\", \"description\":\"Ensimmäisen kortin kuvaus\"}, {\"title\":\"Toisen kortin otsikko\",\"description\":\"Toisen kortin kuvaus\"},{\"title\":\"Viimeisen kortin otsikko\",\"description\":\"Viimeisen kortin kuvaus\"} ]",
|
||||
"create": "Luo",
|
||||
"createBoardPopup-title": "Luo taulu",
|
||||
@@ -237,7 +237,7 @@
|
||||
"custom-field-date": "Päivämäärä",
|
||||
"custom-field-dropdown": "Pudotusvalikko",
|
||||
"custom-field-dropdown-none": "(ei mitään)",
|
||||
"custom-field-dropdown-options": "Luettelon vaihtoehdot",
|
||||
"custom-field-dropdown-options": "Listan vaihtoehdot",
|
||||
"custom-field-dropdown-options-placeholder": "Paina Enter lisätäksesi lisää vaihtoehtoja",
|
||||
"custom-field-dropdown-unknown": "(tuntematon)",
|
||||
"custom-field-number": "Numero",
|
||||
@@ -245,13 +245,13 @@
|
||||
"custom-fields": "Mukautetut kentät",
|
||||
"date": "Päivämäärä",
|
||||
"decline": "Kieltäydy",
|
||||
"default-avatar": "Oletus profiilikuva",
|
||||
"default-avatar": "Oletusprofiilikuva",
|
||||
"delete": "Poista",
|
||||
"deleteCustomFieldPopup-title": "Poista mukautettu kenttä?",
|
||||
"deleteLabelPopup-title": "Poista tunniste?",
|
||||
"description": "Kuvaus",
|
||||
"disambiguateMultiLabelPopup-title": "Yksikäsitteistä tunniste toiminta",
|
||||
"disambiguateMultiMemberPopup-title": "Yksikäsitteistä jäsen toiminta",
|
||||
"disambiguateMultiLabelPopup-title": "Yksikäsitteistä tunnistetoiminta",
|
||||
"disambiguateMultiMemberPopup-title": "Yksikäsitteistä jäsentoiminta",
|
||||
"discard": "Hylkää",
|
||||
"done": "Valmis",
|
||||
"download": "Lataa",
|
||||
@@ -259,7 +259,7 @@
|
||||
"edit-avatar": "Muokkaa profiilikuvaa",
|
||||
"edit-profile": "Muokkaa profiilia",
|
||||
"edit-wip-limit": "Muokkaa WIP-rajaa",
|
||||
"soft-wip-limit": "Pehmeä WIP raja",
|
||||
"soft-wip-limit": "Pehmeä WIP-raja",
|
||||
"editCardStartDatePopup-title": "Muokkaa aloituspäivää",
|
||||
"editCardDueDatePopup-title": "Muokkaa eräpäivää",
|
||||
"editCustomFieldPopup-title": "Muokkaa kenttää",
|
||||
@@ -268,25 +268,25 @@
|
||||
"editNotificationPopup-title": "Muokkaa ilmoituksia",
|
||||
"editProfilePopup-title": "Muokkaa profiilia",
|
||||
"email": "Sähköposti",
|
||||
"email-enrollAccount-subject": "An account created for you on __siteName__",
|
||||
"email-enrollAccount-text": "Hei __user__,\n\nAlkaaksesi käyttämään palvelua, klikkaa allaolevaa linkkiä.\n\n__url__\n\nKiitos.",
|
||||
"email-enrollAccount-subject": "Sinulle on luotu tili palveluun __siteName__",
|
||||
"email-enrollAccount-text": "Hei __user__,\n\nKlikkaa alla olevaa linkkiä aloittaaksesi palvelun käytön.\n\n__url__\n\nKiitos.",
|
||||
"email-fail": "Sähköpostin lähettäminen epäonnistui",
|
||||
"email-fail-text": "Virhe yrittäessä lähettää sähköpostia",
|
||||
"email-invalid": "Virheellinen sähköposti",
|
||||
"email-invite": "Kutsu sähköpostilla",
|
||||
"email-invite-subject": "__inviter__ lähetti sinulle kutsun",
|
||||
"email-invite-text": "Hyvä __user__,\n\n__inviter__ kutsuu sinut liittymään taululle \"__board__\" yhteistyötä varten.\n\nOle hyvä ja seuraa allaolevaa linkkiä:\n\n__url__\n\nKiitos.",
|
||||
"email-resetPassword-subject": "Reset your password on __siteName__",
|
||||
"email-resetPassword-text": "Hei __user__,\n\nNollataksesi salasanasi, klikkaa allaolevaa linkkiä.\n\n__url__\n\nKiitos.",
|
||||
"email-invite-text": "Hyvä __user__,\n\n__inviter__ kutsuu sinut liittymään taululle \"__board__\" yhteistyötä varten.\n\nOle hyvä ja seuraa alla olevaa linkkiä:\n\n__url__\n\nKiitos.",
|
||||
"email-resetPassword-subject": "Nollaa salasanasi palvelussa __siteName__",
|
||||
"email-resetPassword-text": "Hei __user__,\n\nNollataksesi salasanasi, klikkaa alla olevaa linkkiä.\n\n__url__\n\nKiitos.",
|
||||
"email-sent": "Sähköposti lähetetty",
|
||||
"email-verifyEmail-subject": "Varmista sähköpostiosoitteesi osoitteessa __url__",
|
||||
"email-verifyEmail-text": "Hei __user__,\n\nvahvistaaksesi sähköpostiosoitteesi, klikkaa allaolevaa linkkiä.\n\n__url__\n\nKiitos.",
|
||||
"email-verifyEmail-text": "Hei __user__,\n\nvahvistaaksesi sähköpostiosoitteesi, klikkaa alla olevaa linkkiä.\n\n__url__\n\nKiitos.",
|
||||
"enable-wip-limit": "Ota käyttöön WIP-raja",
|
||||
"error-board-doesNotExist": "Tämä taulu ei ole olemassa",
|
||||
"error-board-doesNotExist": "Tätä taulua ei ole olemassa",
|
||||
"error-board-notAdmin": "Tehdäksesi tämän sinun täytyy olla tämän taulun ylläpitäjä",
|
||||
"error-board-notAMember": "Tehdäksesi tämän sinun täytyy olla tämän taulun jäsen",
|
||||
"error-json-malformed": "Tekstisi ei ole kelvollisessa JSON muodossa",
|
||||
"error-json-schema": "JSON tietosi ei sisällä oikeaa tietoa oikeassa muodossa",
|
||||
"error-json-malformed": "Tekstisi ei ole kelvollisessa JSON-muodossa",
|
||||
"error-json-schema": "JSON-tietosi ei sisällä oikeaa tietoa oikeassa muodossa",
|
||||
"error-list-doesNotExist": "Tätä listaa ei ole olemassa",
|
||||
"error-user-doesNotExist": "Tätä käyttäjää ei ole olemassa",
|
||||
"error-user-notAllowSelf": "Et voi kutsua itseäsi",
|
||||
@@ -316,14 +316,14 @@
|
||||
"import-board-c": "Tuo taulu",
|
||||
"import-board-title-trello": "Tuo taulu Trellosta",
|
||||
"import-board-title-wekan": "Tuo taulu edellisestä viennistä",
|
||||
"import-sandstorm-backup-warning": "Älä poista tietoja joita toit alkuperäisestä viennistä tai Trellosta ennenkuin tarkistat onnistuuko sulkea ja avata tämä jyvä uudelleen, vai näkyykö Board not found virhe, joka tarkoittaa tietojen häviämistä.",
|
||||
"import-sandstorm-warning": "Tuotu taulu poistaa kaikki olemassaolevan taulun tiedot ja korvaa ne tuodulla taululla.",
|
||||
"import-sandstorm-backup-warning": "Älä poista tietoja joita toit alkuperäisestä viennistä tai Trellosta ennen kuin tarkistat onnistuuko sulkea ja avata tämä jyvä uudelleen, vai näkyykö Board not found -virhe, joka tarkoittaa tietojen häviämistä.",
|
||||
"import-sandstorm-warning": "Tuotu taulu poistaa kaikki olemassa olevan taulun tiedot ja korvaa ne tuodulla taululla.",
|
||||
"from-trello": "Trellosta",
|
||||
"from-wekan": "Edellisestä viennistä",
|
||||
"import-board-instruction-trello": "Trello taulullasi, mene 'Menu', sitten 'More', 'Print and Export', 'Export JSON', ja kopioi tuloksena saamasi teksti",
|
||||
"import-board-instruction-trello": "Mene Trello-taulullasi 'Menu', sitten 'More', 'Print and Export', 'Export JSON', ja kopioi tuloksena saamasi teksti",
|
||||
"import-board-instruction-wekan": "Taulullasi, mene 'Valikko', sitten 'Vie taulu', ja kopioi teksti ladatusta tiedostosta.",
|
||||
"import-board-instruction-about-errors": "Jos virheitä tulee taulua tuotaessa, joskus tuonti silti toimii, ja taulu on Kaikki taulut sivulla.",
|
||||
"import-json-placeholder": "Liitä kelvollinen JSON tietosi tähän",
|
||||
"import-json-placeholder": "Liitä kelvollinen JSON-tietosi tähän",
|
||||
"import-map-members": "Vastaavat jäsenet",
|
||||
"import-members-map": "Tuomallasi taululla on muutamia jäseniä. Ole hyvä ja valitse tuomiasi jäseniä vastaavat käyttäjäsi",
|
||||
"import-show-user-mapping": "Tarkasta vastaavat jäsenet",
|
||||
@@ -345,17 +345,17 @@
|
||||
"last-admin-desc": "Et voi vaihtaa rooleja koska täytyy olla olemassa ainakin yksi ylläpitäjä.",
|
||||
"leave-board": "Jää pois taululta",
|
||||
"leave-board-pop": "Haluatko varmasti poistua taululta __boardTitle__? Sinut poistetaan kaikista tämän taulun korteista.",
|
||||
"leaveBoardPopup-title": "Jää pois taululta ?",
|
||||
"leaveBoardPopup-title": "Poistu taululta?",
|
||||
"link-card": "Linkki tähän korttiin",
|
||||
"list-archive-cards": "Siirrä kaikki tämän listan kortit Arkistoon",
|
||||
"list-archive-cards-pop": "Tämä poistaa kaikki tämän listan kortit taululta. Nähdäksesi Arkistossa olevat kortit ja tuodaksesi ne takaisin taululle, klikkaa “Valikko” > “Arkisto”.",
|
||||
"list-move-cards": "Siirrä kaikki kortit tässä listassa",
|
||||
"list-select-cards": "Valitse kaikki kortit tässä listassa",
|
||||
"set-color-list": "Aseta väri",
|
||||
"listActionPopup-title": "Listaa toimet",
|
||||
"swimlaneActionPopup-title": "Swimlane toimet",
|
||||
"listActionPopup-title": "Listatoimet",
|
||||
"swimlaneActionPopup-title": "Swimlane-toimet",
|
||||
"swimlaneAddPopup-title": "Lisää Swimlane alle",
|
||||
"listImportCardPopup-title": "Tuo Trello kortti",
|
||||
"listImportCardPopup-title": "Tuo Trello-kortti",
|
||||
"listMorePopup-title": "Lisää",
|
||||
"link-list": "Linkki tähän listaan",
|
||||
"list-delete-pop": "Kaikki toimet poistetaan toimintasyötteestä ja listan poistaminen on lopullista. Tätä ei pysty peruuttamaan.",
|
||||
@@ -365,7 +365,7 @@
|
||||
"log-out": "Kirjaudu ulos",
|
||||
"log-in": "Kirjaudu sisään",
|
||||
"loginPopup-title": "Kirjaudu sisään",
|
||||
"memberMenuPopup-title": "Jäsen asetukset",
|
||||
"memberMenuPopup-title": "Jäsenasetukset",
|
||||
"members": "Jäsenet",
|
||||
"menu": "Valikko",
|
||||
"move-selection": "Siirrä valinta",
|
||||
@@ -390,7 +390,7 @@
|
||||
"notify-watch": "Vastaanota päivityksiä kaikilta tauluilta, listoilta tai korteilta joita seuraat.",
|
||||
"optional": "valinnainen",
|
||||
"or": "tai",
|
||||
"page-maybe-private": "Tämä sivu voi olla yksityinen. Voit ehkä pystyä näkemään sen <a href='%s'>kirjautumalla sisään</a>.",
|
||||
"page-maybe-private": "Tämä sivu voi olla yksityinen. Saatat nähdä sen <a href='%s'>kirjautumalla sisään</a>.",
|
||||
"page-not-found": "Sivua ei löytynyt.",
|
||||
"password": "Salasana",
|
||||
"paste-or-dragdrop": "liittääksesi, tai vedä & pudota kuvatiedosto siihen (vain kuva)",
|
||||
@@ -407,7 +407,7 @@
|
||||
"remove-cover": "Poista kansi",
|
||||
"remove-from-board": "Poista taululta",
|
||||
"remove-label": "Poista tunniste",
|
||||
"listDeletePopup-title": "Poista lista ?",
|
||||
"listDeletePopup-title": "Poista lista?",
|
||||
"remove-member": "Poista jäsen",
|
||||
"remove-member-from-card": "Poista kortilta",
|
||||
"remove-member-pop": "Poista __name__ (__username__) taululta __boardTitle__? Jäsen poistetaan kaikilta taulun korteilta. Heille lähetetään ilmoitus.",
|
||||
@@ -419,7 +419,7 @@
|
||||
"search": "Etsi",
|
||||
"rules": "Säännöt",
|
||||
"search-cards": "Etsi korttien otsikoista ja kuvauksista tällä taululla",
|
||||
"search-example": "Teksti jota etsitään?",
|
||||
"search-example": "Etsittävä teksti?",
|
||||
"select-color": "Valitse väri",
|
||||
"set-wip-limit-value": "Aseta tämän listan tehtävien enimmäismäärä",
|
||||
"setWipLimitPopup-title": "Aseta WIP-raja",
|
||||
@@ -429,9 +429,9 @@
|
||||
"shortcut-clear-filters": "Poista kaikki suodattimet",
|
||||
"shortcut-close-dialog": "Sulje valintaikkuna",
|
||||
"shortcut-filter-my-cards": "Suodata korttini",
|
||||
"shortcut-show-shortcuts": "Tuo esiin tämä pikavalinta lista",
|
||||
"shortcut-toggle-filterbar": "Muokkaa suodatus sivupalkin näkyvyyttä",
|
||||
"shortcut-toggle-sidebar": "Muokkaa taulu sivupalkin näkyvyyttä",
|
||||
"shortcut-show-shortcuts": "Tuo esiin tämä pikavalintalista",
|
||||
"shortcut-toggle-filterbar": "Muokkaa suodatussivupalkin näkyvyyttä",
|
||||
"shortcut-toggle-sidebar": "Muokkaa taulusivupalkin näkyvyyttä",
|
||||
"show-cards-minimum-count": "Näytä korttien lukumäärä jos lista sisältää enemmän kuin",
|
||||
"sidebar-open": "Avaa sivupalkki",
|
||||
"sidebar-close": "Sulje sivupalkki",
|
||||
@@ -446,8 +446,8 @@
|
||||
"spent-time-hours": "Käytetty aika (tuntia)",
|
||||
"overtime-hours": "Ylityö (tuntia)",
|
||||
"overtime": "Ylityö",
|
||||
"has-overtime-cards": "Sisältää ylityö kortteja",
|
||||
"has-spenttime-cards": "Sisältää käytetty aika kortteja",
|
||||
"has-overtime-cards": "Sisältää ylityökortteja",
|
||||
"has-spenttime-cards": "Sisältää käytetty aika -kortteja",
|
||||
"time": "Aika",
|
||||
"title": "Otsikko",
|
||||
"tracking": "Ilmoitukset",
|
||||
@@ -465,13 +465,13 @@
|
||||
"watch": "Seuraa",
|
||||
"watching": "Seurataan",
|
||||
"watching-info": "Sinulle ilmoitetaan tämän taulun muutoksista",
|
||||
"welcome-board": "Tervetuloa taulu",
|
||||
"welcome-board": "Tervetuloa-taulu",
|
||||
"welcome-swimlane": "Merkkipaalu 1",
|
||||
"welcome-list1": "Perusasiat",
|
||||
"welcome-list2": "Edistynyt",
|
||||
"card-templates-swimlane": "Kortti mallit",
|
||||
"list-templates-swimlane": "Lista mallit",
|
||||
"board-templates-swimlane": "Taulu mallit",
|
||||
"card-templates-swimlane": "Korttimallit",
|
||||
"list-templates-swimlane": "Listamallit",
|
||||
"board-templates-swimlane": "Taulumallit",
|
||||
"what-to-do": "Mitä haluat tehdä?",
|
||||
"wipLimitErrorPopup-title": "Virheellinen WIP-raja",
|
||||
"wipLimitErrorPopup-dialog-pt1": "Tässä listassa olevien tehtävien määrä on korkeampi kuin asettamasi WIP-raja.",
|
||||
@@ -480,36 +480,36 @@
|
||||
"settings": "Asetukset",
|
||||
"people": "Ihmiset",
|
||||
"registration": "Rekisteröinti",
|
||||
"disable-self-registration": "Poista käytöstä itse-rekisteröityminen",
|
||||
"disable-self-registration": "Poista käytöstä itserekisteröityminen",
|
||||
"invite": "Kutsu",
|
||||
"invite-people": "Kutsu ihmisiä",
|
||||
"to-boards": "Taulu(i)lle",
|
||||
"email-addresses": "Sähköpostiosoite",
|
||||
"smtp-host-description": "SMTP palvelimen osoite jolla sähköpostit lähetetään.",
|
||||
"smtp-port-description": "Portti jota STMP palvelimesi käyttää lähteville sähköposteille.",
|
||||
"smtp-tls-description": "Ota käyttöön TLS tuki SMTP palvelimelle",
|
||||
"smtp-host": "SMTP isäntä",
|
||||
"smtp-port": "SMTP portti",
|
||||
"smtp-host-description": "SMTP-palvelimen osoite jolla sähköpostit lähetetään.",
|
||||
"smtp-port-description": "STMP-palvelimesi käyttämä lähteville sähköposteille tarkoitettu portti.",
|
||||
"smtp-tls-description": "Ota käyttöön TLS-tuki SMTP-palvelimelle",
|
||||
"smtp-host": "SMTP-isäntä",
|
||||
"smtp-port": "SMTP-portti",
|
||||
"smtp-username": "Käyttäjätunnus",
|
||||
"smtp-password": "Salasana",
|
||||
"smtp-tls": "TLS tuki",
|
||||
"smtp-tls": "TLS-tuki",
|
||||
"send-from": "Lähettäjä",
|
||||
"send-smtp-test": "Lähetä testi sähköposti itsellesi",
|
||||
"send-smtp-test": "Lähetä testisähköposti itsellesi",
|
||||
"invitation-code": "Kutsukoodi",
|
||||
"email-invite-register-subject": "__inviter__ lähetti sinulle kutsun",
|
||||
"email-invite-register-text": "Hei __user__,\n\n__inviter__ kutsuu sinut mukaan kanban taulun käyttöön.\n\nOle hyvä ja seuraa allaolevaa linkkiä:\n__url__\n\nJa kutsukoodisi on: __icode__\n\nKiitos.",
|
||||
"email-smtp-test-subject": "SMTP testi sähköposti",
|
||||
"email-invite-register-text": "Hei __user__,\n\n__inviter__ kutsuu sinut mukaan kanban-taulun käyttöön.\n\nOle hyvä ja seuraa alla olevaa linkkiä:\n__url__\n\nKutsukoodisi on: __icode__\n\nKiitos.",
|
||||
"email-smtp-test-subject": "SMTP-testisähköposti",
|
||||
"email-smtp-test-text": "Olet onnistuneesti lähettänyt sähköpostin",
|
||||
"error-invitation-code-not-exist": "Kutsukoodi ei ole olemassa",
|
||||
"error-invitation-code-not-exist": "Kutsukoodia ei ole olemassa",
|
||||
"error-notAuthorized": "Sinulla ei ole oikeutta tarkastella tätä sivua.",
|
||||
"outgoing-webhooks": "Lähtevät Webkoukut",
|
||||
"outgoingWebhooksPopup-title": "Lähtevät Webkoukut",
|
||||
"boardCardTitlePopup-title": "Kortin otsikko suodatin",
|
||||
"boardCardTitlePopup-title": "Kortin otsikkosuodatin",
|
||||
"new-outgoing-webhook": "Uusi lähtevä Webkoukku",
|
||||
"no-name": "(Tuntematon)",
|
||||
"Node_version": "Node versio",
|
||||
"Node_version": "Node-versio",
|
||||
"OS_Arch": "Käyttöjärjestelmän arkkitehtuuri",
|
||||
"OS_Cpus": "Käyttöjärjestelmän CPU määrä",
|
||||
"OS_Cpus": "Käyttöjärjestelmän CPU-määrä",
|
||||
"OS_Freemem": "Käyttöjärjestelmän vapaa muisti",
|
||||
"OS_Loadavg": "Käyttöjärjestelmän kuorman keskiarvo",
|
||||
"OS_Platform": "Käyttöjärjestelmäalusta",
|
||||
@@ -548,13 +548,13 @@
|
||||
"delete-board-confirm-popup": "Kaikki listat, kortit, tunnisteet ja toimet poistetaan ja et pysty palauttamaan taulun sisältöä. Tätä ei voi peruuttaa.",
|
||||
"boardDeletePopup-title": "Poista taulu?",
|
||||
"delete-board": "Poista taulu",
|
||||
"default-subtasks-board": "Alitehtävät __board__ taululle",
|
||||
"default-subtasks-board": "Alitehtävät taululle __board__",
|
||||
"default": "Oletus",
|
||||
"queue": "Jono",
|
||||
"subtask-settings": "Alitehtävä asetukset",
|
||||
"boardSubtaskSettingsPopup-title": "Taulu alitehtävien asetukset",
|
||||
"subtask-settings": "Alitehtävä-asetukset",
|
||||
"boardSubtaskSettingsPopup-title": "Taulualitehtävien asetukset",
|
||||
"show-subtasks-field": "Korteilla voi olla alitehtäviä",
|
||||
"deposit-subtasks-board": "Tallenta alitehtävät tälle taululle:",
|
||||
"deposit-subtasks-board": "Talleta alitehtävät tälle taululle:",
|
||||
"deposit-subtasks-list": "Laskeutumislista alatehtäville tallennettu tänne:",
|
||||
"show-parent-in-minicard": "Näytä ylätehtävä minikortilla:",
|
||||
"prefix-with-full-path": "Etuliite koko polulla",
|
||||
@@ -562,7 +562,7 @@
|
||||
"subtext-with-full-path": "Aliteksti koko polulla",
|
||||
"subtext-with-parent": "Aliteksti ylätehtävällä",
|
||||
"change-card-parent": "Muuta kortin ylätehtävää",
|
||||
"parent-card": "Ylätehtävä kortti",
|
||||
"parent-card": "Ylätehtäväkortti",
|
||||
"source-board": "Lähdetaulu",
|
||||
"no-parent": "Älä näytä ylätehtävää",
|
||||
"activity-added-label": "lisätty tunniste '%s' kohteeseen %s",
|
||||
@@ -576,7 +576,7 @@
|
||||
"r-rule": "Sääntö",
|
||||
"r-add-trigger": "Lisää liipaisin",
|
||||
"r-add-action": "Lisää toimi",
|
||||
"r-board-rules": "Taulu säännöt",
|
||||
"r-board-rules": "Taulusäännöt",
|
||||
"r-add-rule": "Lisää sääntö",
|
||||
"r-view-rule": "Näytä sääntö",
|
||||
"r-delete-rule": "Poista sääntö",
|
||||
@@ -682,10 +682,10 @@
|
||||
"error-undefined": "Jotain meni pieleen",
|
||||
"error-ldap-login": "Virhe tapahtui yrittäessä kirjautua sisään",
|
||||
"display-authentication-method": "Näytä kirjautumistapa",
|
||||
"default-authentication-method": "Oletus kirjautumistapa",
|
||||
"default-authentication-method": "Oletuskirjautumistapa",
|
||||
"duplicate-board": "Tee kaksoiskappale taulusta",
|
||||
"people-number": "Ihmisten määrä on:",
|
||||
"swimlaneDeletePopup-title": "Poista Swimlane ?",
|
||||
"swimlaneDeletePopup-title": "Poista Swimlane?",
|
||||
"swimlane-delete-pop": "Kaikki toimet poistetaan toimintasyötteestä ja swimlanen poistaminen on lopullista. Tätä ei pysty peruuttamaan.",
|
||||
"restore-all": "Palauta kaikki",
|
||||
"delete-all": "Poista kaikki",
|
||||
|
||||
@@ -867,6 +867,22 @@ if (Meteor.isServer) {
|
||||
} else throw new Meteor.Error('error-board-doesNotExist');
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
archiveBoard(boardId) {
|
||||
check(boardId, String);
|
||||
const board = Boards.findOne(boardId);
|
||||
if (board) {
|
||||
const userId = Meteor.userId();
|
||||
const index = board.memberIndex(userId);
|
||||
if (index >= 0) {
|
||||
board.archive();
|
||||
return true;
|
||||
} else throw new Meteor.Error('error-board-notAMember');
|
||||
} else throw new Meteor.Error('error-board-doesNotExist');
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (Meteor.isServer) {
|
||||
|
||||
@@ -1685,6 +1685,7 @@ if (Meteor.isServer) {
|
||||
* @param {string} boardId the board ID of the new card
|
||||
* @param {string} listId the list ID of the new card
|
||||
* @param {string} authorID the user ID of the person owning the card
|
||||
* @param {string} parentId the parent ID of the new card
|
||||
* @param {string} title the title of the new card
|
||||
* @param {string} description the description of the new card
|
||||
* @param {string} swimlaneId the swimlane ID of the new card
|
||||
@@ -1695,6 +1696,7 @@ if (Meteor.isServer) {
|
||||
Authentication.checkUserId(req.userId);
|
||||
const paramBoardId = req.params.boardId;
|
||||
const paramListId = req.params.listId;
|
||||
const paramParentId = req.params.parentId;
|
||||
const currentCards = Cards.find({
|
||||
listId: paramListId,
|
||||
archived: false,
|
||||
@@ -1708,6 +1710,7 @@ if (Meteor.isServer) {
|
||||
title: req.body.title,
|
||||
boardId: paramBoardId,
|
||||
listId: paramListId,
|
||||
parentId: paramParentId,
|
||||
description: req.body.description,
|
||||
userId: req.body.authorId,
|
||||
swimlaneId: req.body.swimlaneId,
|
||||
@@ -1761,6 +1764,7 @@ if (Meteor.isServer) {
|
||||
* @param {string} [listId] the new list ID of the card (move operation)
|
||||
* @param {string} [description] the new description of the card
|
||||
* @param {string} [authorId] change the owner of the card
|
||||
* @param {string} [parentId] change the parent of the card
|
||||
* @param {string} [labelIds] the new list of label IDs attached to the card
|
||||
* @param {string} [swimlaneId] the new swimlane ID of the card
|
||||
* @param {string} [members] the new list of member IDs attached to the card
|
||||
@@ -1817,6 +1821,19 @@ if (Meteor.isServer) {
|
||||
}, paramListId);
|
||||
|
||||
}
|
||||
if (req.body.hasOwnProperty('parentId')) {
|
||||
const newParentId = req.body.parentId;
|
||||
Cards.direct.update({
|
||||
_id: paramCardId,
|
||||
listId: paramListId,
|
||||
boardId: paramBoardId,
|
||||
archived: false,
|
||||
}, {
|
||||
$set: {
|
||||
parentId: newParentId,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (req.body.hasOwnProperty('description')) {
|
||||
const newDescription = req.body.description;
|
||||
Cards.direct.update({
|
||||
|
||||
+15
-8
@@ -6,27 +6,34 @@ if (Meteor.isServer) {
|
||||
// `ApiRoutes.path('boards/export', boardId)``
|
||||
// on the client instead of copy/pasting the route path manually between the
|
||||
// client and the server.
|
||||
/*
|
||||
* This route is used to export the board FROM THE APPLICATION.
|
||||
* If user is already logged-in, pass loginToken as param "authToken":
|
||||
* '/api/boards/:boardId/export?authToken=:token'
|
||||
/**
|
||||
* @operation export
|
||||
* @tag Boards
|
||||
*
|
||||
* @summary This route is used to export the board.
|
||||
*
|
||||
* @description If user is already logged-in, pass loginToken as param
|
||||
* "authToken": '/api/boards/:boardId/export?authToken=:token'
|
||||
*
|
||||
* See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/
|
||||
* for detailed explanations
|
||||
*
|
||||
* @param {string} boardId the ID of the board we are exporting
|
||||
* @param {string} authToken the loginToken
|
||||
*/
|
||||
|
||||
|
||||
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
|
||||
// then fallback to parameter
|
||||
|
||||
const loginToken = req.query.authToken;
|
||||
if (loginToken) {
|
||||
const hashToken = Accounts._hashLoginToken(loginToken);
|
||||
user = Meteor.users.findOne({
|
||||
'services.resume.loginTokens.hashedToken': hashToken,
|
||||
});
|
||||
} else if (!Meteor.settings.public.sandstorm) {
|
||||
Authentication.checkUserId(req.userId);
|
||||
user = Users.findOne({ _id: req.userId, isAdmin: true });
|
||||
}
|
||||
|
||||
const exporter = new Exporter(boardId);
|
||||
|
||||
@@ -678,7 +678,7 @@ def parse_schemas(schemas_dir):
|
||||
data = ''.join(f.readlines())
|
||||
try:
|
||||
# if the file failed, it's likely it doesn't contain a schema
|
||||
program = esprima.parseScript(data, options={'comment': True, 'loc': True})
|
||||
program = esprima.parseModule(data, options={'comment': True, 'loc': True})
|
||||
except:
|
||||
continue
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wekan",
|
||||
"version": "v2.66.0",
|
||||
"version": "v2.74.0",
|
||||
"description": "Open-Source kanban",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
import ldapjs from 'ldapjs';
|
||||
import util from 'util';
|
||||
import Bunyan from 'bunyan';
|
||||
import { log_debug, log_info, log_warn, log_error } from './logger';
|
||||
import {log_debug, log_info, log_warn, log_error} from './logger';
|
||||
|
||||
|
||||
export default class LDAP {
|
||||
constructor(){
|
||||
constructor() {
|
||||
this.ldapjs = ldapjs;
|
||||
|
||||
this.connected = false;
|
||||
|
||||
this.options = {
|
||||
host: this.constructor.settings_get('LDAP_HOST'),
|
||||
port: this.constructor.settings_get('LDAP_PORT'),
|
||||
Reconnect: this.constructor.settings_get('LDAP_RECONNECT'),
|
||||
timeout: this.constructor.settings_get('LDAP_TIMEOUT'),
|
||||
connect_timeout: this.constructor.settings_get('LDAP_CONNECT_TIMEOUT'),
|
||||
idle_timeout: this.constructor.settings_get('LDAP_IDLE_TIMEOUT'),
|
||||
encryption: this.constructor.settings_get('LDAP_ENCRYPTION'),
|
||||
ca_cert: this.constructor.settings_get('LDAP_CA_CERT'),
|
||||
reject_unauthorized: this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') || false,
|
||||
Authentication: this.constructor.settings_get('LDAP_AUTHENTIFICATION'),
|
||||
Authentication_UserDN: this.constructor.settings_get('LDAP_AUTHENTIFICATION_USERDN'),
|
||||
Authentication_Password: this.constructor.settings_get('LDAP_AUTHENTIFICATION_PASSWORD'),
|
||||
Authentication_Fallback: this.constructor.settings_get('LDAP_LOGIN_FALLBACK'),
|
||||
BaseDN: this.constructor.settings_get('LDAP_BASEDN'),
|
||||
Internal_Log_Level: this.constructor.settings_get('INTERNAL_LOG_LEVEL'),
|
||||
User_Search_Filter: this.constructor.settings_get('LDAP_USER_SEARCH_FILTER'),
|
||||
User_Search_Scope: this.constructor.settings_get('LDAP_USER_SEARCH_SCOPE'),
|
||||
User_Search_Field: this.constructor.settings_get('LDAP_USER_SEARCH_FIELD'),
|
||||
Search_Page_Size: this.constructor.settings_get('LDAP_SEARCH_PAGE_SIZE'),
|
||||
Search_Size_Limit: this.constructor.settings_get('LDAP_SEARCH_SIZE_LIMIT'),
|
||||
group_filter_enabled: this.constructor.settings_get('LDAP_GROUP_FILTER_ENABLE'),
|
||||
group_filter_object_class: this.constructor.settings_get('LDAP_GROUP_FILTER_OBJECTCLASS'),
|
||||
group_filter_group_id_attribute: this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE'),
|
||||
host : this.constructor.settings_get('LDAP_HOST'),
|
||||
port : this.constructor.settings_get('LDAP_PORT'),
|
||||
Reconnect : this.constructor.settings_get('LDAP_RECONNECT'),
|
||||
timeout : this.constructor.settings_get('LDAP_TIMEOUT'),
|
||||
connect_timeout : this.constructor.settings_get('LDAP_CONNECT_TIMEOUT'),
|
||||
idle_timeout : this.constructor.settings_get('LDAP_IDLE_TIMEOUT'),
|
||||
encryption : this.constructor.settings_get('LDAP_ENCRYPTION'),
|
||||
ca_cert : this.constructor.settings_get('LDAP_CA_CERT'),
|
||||
reject_unauthorized : this.constructor.settings_get('LDAP_REJECT_UNAUTHORIZED') || false,
|
||||
Authentication : this.constructor.settings_get('LDAP_AUTHENTIFICATION'),
|
||||
Authentication_UserDN : this.constructor.settings_get('LDAP_AUTHENTIFICATION_USERDN'),
|
||||
Authentication_Password : this.constructor.settings_get('LDAP_AUTHENTIFICATION_PASSWORD'),
|
||||
Authentication_Fallback : this.constructor.settings_get('LDAP_LOGIN_FALLBACK'),
|
||||
BaseDN : this.constructor.settings_get('LDAP_BASEDN'),
|
||||
Internal_Log_Level : this.constructor.settings_get('INTERNAL_LOG_LEVEL'),
|
||||
User_Authentication : this.constructor.settings_get('LDAP_USER_AUTHENTICATION'),
|
||||
User_Attributes : this.constructor.settings_get('LDAP_USER_ATTRIBUTES'),
|
||||
User_Search_Filter : this.constructor.settings_get('LDAP_USER_SEARCH_FILTER'),
|
||||
User_Search_Scope : this.constructor.settings_get('LDAP_USER_SEARCH_SCOPE'),
|
||||
User_Search_Field : this.constructor.settings_get('LDAP_USER_SEARCH_FIELD'),
|
||||
Search_Page_Size : this.constructor.settings_get('LDAP_SEARCH_PAGE_SIZE'),
|
||||
Search_Size_Limit : this.constructor.settings_get('LDAP_SEARCH_SIZE_LIMIT'),
|
||||
group_filter_enabled : this.constructor.settings_get('LDAP_GROUP_FILTER_ENABLE'),
|
||||
group_filter_object_class : this.constructor.settings_get('LDAP_GROUP_FILTER_OBJECTCLASS'),
|
||||
group_filter_group_id_attribute : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE'),
|
||||
group_filter_group_member_attribute: this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE'),
|
||||
group_filter_group_member_format: this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT'),
|
||||
group_filter_group_name: this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_NAME'),
|
||||
group_filter_group_member_format : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT'),
|
||||
group_filter_group_name : this.constructor.settings_get('LDAP_GROUP_FILTER_GROUP_NAME'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,14 +55,16 @@ export default class LDAP {
|
||||
log_warn(`Lookup for unset variable: ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
connectSync(...args) {
|
||||
if (!this._connectSync) {
|
||||
if (!this._connectSync) {
|
||||
this._connectSync = Meteor.wrapAsync(this.connectAsync, this);
|
||||
}
|
||||
return this._connectSync(...args);
|
||||
}
|
||||
|
||||
searchAllSync(...args) {
|
||||
|
||||
if (!this._searchAllSync) {
|
||||
this._searchAllSync = Meteor.wrapAsync(this.searchAllAsync, this);
|
||||
}
|
||||
@@ -72,19 +77,19 @@ export default class LDAP {
|
||||
let replied = false;
|
||||
|
||||
const connectionOptions = {
|
||||
url: `${ this.options.host }:${ this.options.port }`,
|
||||
timeout: this.options.timeout,
|
||||
url : `${this.options.host}:${this.options.port}`,
|
||||
timeout : this.options.timeout,
|
||||
connectTimeout: this.options.connect_timeout,
|
||||
idleTimeout: this.options.idle_timeout,
|
||||
reconnect: this.options.Reconnect,
|
||||
idleTimeout : this.options.idle_timeout,
|
||||
reconnect : this.options.Reconnect,
|
||||
};
|
||||
|
||||
if (this.options.Internal_Log_Level !== 'disabled') {
|
||||
connectionOptions.log = new Bunyan({
|
||||
name: 'ldapjs',
|
||||
name : 'ldapjs',
|
||||
component: 'client',
|
||||
stream: process.stderr,
|
||||
level: this.options.Internal_Log_Level,
|
||||
stream : process.stderr,
|
||||
level : this.options.Internal_Log_Level,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -95,8 +100,8 @@ export default class LDAP {
|
||||
if (this.options.ca_cert && this.options.ca_cert !== '') {
|
||||
// Split CA cert into array of strings
|
||||
const chainLines = this.constructor.settings_get('LDAP_CA_CERT').split('\n');
|
||||
let cert = [];
|
||||
const ca = [];
|
||||
let cert = [];
|
||||
const ca = [];
|
||||
chainLines.forEach((line) => {
|
||||
cert.push(line);
|
||||
if (line.match(/-END CERTIFICATE-/)) {
|
||||
@@ -108,14 +113,14 @@ export default class LDAP {
|
||||
}
|
||||
|
||||
if (this.options.encryption === 'ssl') {
|
||||
connectionOptions.url = `ldaps://${ connectionOptions.url }`;
|
||||
connectionOptions.url = `ldaps://${connectionOptions.url}`;
|
||||
connectionOptions.tlsOptions = tlsOptions;
|
||||
} else {
|
||||
connectionOptions.url = `ldap://${ connectionOptions.url }`;
|
||||
connectionOptions.url = `ldap://${connectionOptions.url}`;
|
||||
}
|
||||
|
||||
log_info('Connecting', connectionOptions.url);
|
||||
log_debug(`connectionOptions${ util.inspect(connectionOptions)}`);
|
||||
log_debug(`connectionOptions${util.inspect(connectionOptions)}`);
|
||||
|
||||
this.client = ldapjs.createClient(connectionOptions);
|
||||
|
||||
@@ -189,23 +194,42 @@ export default class LDAP {
|
||||
|
||||
if (this.options.User_Search_Filter !== '') {
|
||||
if (this.options.User_Search_Filter[0] === '(') {
|
||||
filter.push(`${ this.options.User_Search_Filter }`);
|
||||
filter.push(`${this.options.User_Search_Filter}`);
|
||||
} else {
|
||||
filter.push(`(${ this.options.User_Search_Filter })`);
|
||||
filter.push(`(${this.options.User_Search_Filter})`);
|
||||
}
|
||||
}
|
||||
|
||||
const usernameFilter = this.options.User_Search_Field.split(',').map((item) => `(${ item }=${ username })`);
|
||||
const usernameFilter = this.options.User_Search_Field.split(',').map((item) => `(${item}=${username})`);
|
||||
|
||||
if (usernameFilter.length === 0) {
|
||||
log_error('LDAP_LDAP_User_Search_Field not defined');
|
||||
} else if (usernameFilter.length === 1) {
|
||||
filter.push(`${ usernameFilter[0] }`);
|
||||
filter.push(`${usernameFilter[0]}`);
|
||||
} else {
|
||||
filter.push(`(|${ usernameFilter.join('') })`);
|
||||
filter.push(`(|${usernameFilter.join('')})`);
|
||||
}
|
||||
|
||||
return `(&${ filter.join('') })`;
|
||||
return `(&${filter.join('')})`;
|
||||
}
|
||||
|
||||
bindUserIfNecessary(username, password) {
|
||||
|
||||
if (this.domainBinded === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.options.User_Authentication) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!this.options.BaseDN) throw new Error('BaseDN is not provided');
|
||||
|
||||
const userDn = `uid=${username},${this.options.BaseDN}`;
|
||||
|
||||
this.bindSync(userDn, password);
|
||||
this.domainBinded = true;
|
||||
}
|
||||
|
||||
bindIfNecessary() {
|
||||
@@ -218,22 +242,24 @@ export default class LDAP {
|
||||
}
|
||||
|
||||
log_info('Binding UserDN', this.options.Authentication_UserDN);
|
||||
|
||||
this.bindSync(this.options.Authentication_UserDN, this.options.Authentication_Password);
|
||||
this.domainBinded = true;
|
||||
}
|
||||
|
||||
searchUsersSync(username, page) {
|
||||
this.bindIfNecessary();
|
||||
|
||||
const searchOptions = {
|
||||
filter: this.getUserFilter(username),
|
||||
scope: this.options.User_Search_Scope || 'sub',
|
||||
filter : this.getUserFilter(username),
|
||||
scope : this.options.User_Search_Scope || 'sub',
|
||||
sizeLimit: this.options.Search_Size_Limit,
|
||||
};
|
||||
|
||||
if (!!this.options.User_Attributes) searchOptions.attributes = this.options.User_Attributes.split(',');
|
||||
|
||||
if (this.options.Search_Page_Size > 0) {
|
||||
searchOptions.paged = {
|
||||
pageSize: this.options.Search_Page_Size,
|
||||
pageSize : this.options.Search_Page_Size,
|
||||
pagePause: !!page,
|
||||
};
|
||||
}
|
||||
@@ -266,11 +292,11 @@ export default class LDAP {
|
||||
Unique_Identifier_Field.forEach((item) => {
|
||||
filters.push(new this.ldapjs.filters.EqualityFilter({
|
||||
attribute: item,
|
||||
value: new Buffer(id, 'hex'),
|
||||
value : new Buffer(id, 'hex'),
|
||||
}));
|
||||
});
|
||||
|
||||
filter = new this.ldapjs.filters.OrFilter({filters});
|
||||
filter = new this.ldapjs.filters.OrFilter({ filters });
|
||||
}
|
||||
|
||||
const searchOptions = {
|
||||
@@ -300,7 +326,7 @@ export default class LDAP {
|
||||
|
||||
const searchOptions = {
|
||||
filter: this.getUserFilter(username),
|
||||
scope: this.options.User_Search_Scope || 'sub',
|
||||
scope : this.options.User_Search_Scope || 'sub',
|
||||
};
|
||||
|
||||
log_info('Searching user', username);
|
||||
@@ -320,7 +346,7 @@ export default class LDAP {
|
||||
return result[0];
|
||||
}
|
||||
|
||||
getUserGroups(username, ldapUser){
|
||||
getUserGroups(username, ldapUser) {
|
||||
if (!this.options.group_filter_enabled) {
|
||||
return true;
|
||||
}
|
||||
@@ -328,13 +354,13 @@ export default class LDAP {
|
||||
const filter = ['(&'];
|
||||
|
||||
if (this.options.group_filter_object_class !== '') {
|
||||
filter.push(`(objectclass=${ this.options.group_filter_object_class })`);
|
||||
filter.push(`(objectclass=${this.options.group_filter_object_class})`);
|
||||
}
|
||||
|
||||
if (this.options.group_filter_group_member_attribute !== '') {
|
||||
const format_value = ldapUser[this.options.group_filter_group_member_format];
|
||||
if( format_value ) {
|
||||
filter.push(`(${ this.options.group_filter_group_member_attribute }=${ format_value })`);
|
||||
if (format_value) {
|
||||
filter.push(`(${this.options.group_filter_group_member_attribute}=${format_value})`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +368,7 @@ export default class LDAP {
|
||||
|
||||
const searchOptions = {
|
||||
filter: filter.join('').replace(/#{username}/g, username),
|
||||
scope: 'sub',
|
||||
scope : 'sub',
|
||||
};
|
||||
|
||||
log_debug('Group list filter LDAP:', searchOptions.filter);
|
||||
@@ -354,11 +380,11 @@ export default class LDAP {
|
||||
}
|
||||
|
||||
const grp_identifier = this.options.group_filter_group_id_attribute || 'cn';
|
||||
const groups = [];
|
||||
const groups = [];
|
||||
result.map((item) => {
|
||||
groups.push( item[ grp_identifier ] );
|
||||
groups.push(item[grp_identifier]);
|
||||
});
|
||||
log_debug(`Groups: ${ groups.join(', ')}`);
|
||||
log_debug(`Groups: ${groups.join(', ')}`);
|
||||
return groups;
|
||||
|
||||
}
|
||||
@@ -373,24 +399,24 @@ export default class LDAP {
|
||||
const filter = ['(&'];
|
||||
|
||||
if (this.options.group_filter_object_class !== '') {
|
||||
filter.push(`(objectclass=${ this.options.group_filter_object_class })`);
|
||||
filter.push(`(objectclass=${this.options.group_filter_object_class})`);
|
||||
}
|
||||
|
||||
if (this.options.group_filter_group_member_attribute !== '') {
|
||||
const format_value = ldapUser[this.options.group_filter_group_member_format];
|
||||
if( format_value ) {
|
||||
filter.push(`(${ this.options.group_filter_group_member_attribute }=${ format_value })`);
|
||||
if (format_value) {
|
||||
filter.push(`(${this.options.group_filter_group_member_attribute}=${format_value})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.group_filter_group_id_attribute !== '') {
|
||||
filter.push(`(${ this.options.group_filter_group_id_attribute }=${ this.options.group_filter_group_name })`);
|
||||
filter.push(`(${this.options.group_filter_group_id_attribute}=${this.options.group_filter_group_name})`);
|
||||
}
|
||||
filter.push(')');
|
||||
|
||||
const searchOptions = {
|
||||
filter: filter.join('').replace(/#{username}/g, username),
|
||||
scope: 'sub',
|
||||
scope : 'sub',
|
||||
};
|
||||
|
||||
log_debug('Group filter LDAP:', searchOptions.filter);
|
||||
@@ -426,15 +452,17 @@ export default class LDAP {
|
||||
searchAllPaged(BaseDN, options, page) {
|
||||
this.bindIfNecessary();
|
||||
|
||||
const processPage = ({entries, title, end, next}) => {
|
||||
const processPage = ({ entries, title, end, next }) => {
|
||||
log_info(title);
|
||||
// Force LDAP idle to wait the record processing
|
||||
this.client._updateIdle(true);
|
||||
page(null, entries, {end, next: () => {
|
||||
// Reset idle timer
|
||||
this.client._updateIdle();
|
||||
next && next();
|
||||
}});
|
||||
page(null, entries, {
|
||||
end, next: () => {
|
||||
// Reset idle timer
|
||||
this.client._updateIdle();
|
||||
next && next();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.client.search(BaseDN, options, (error, res) => {
|
||||
@@ -461,7 +489,7 @@ export default class LDAP {
|
||||
processPage({
|
||||
entries,
|
||||
title: 'Internal Page',
|
||||
end: false,
|
||||
end : false,
|
||||
});
|
||||
entries = [];
|
||||
}
|
||||
@@ -473,14 +501,14 @@ export default class LDAP {
|
||||
processPage({
|
||||
entries,
|
||||
title: 'Final Page',
|
||||
end: true,
|
||||
end : true,
|
||||
});
|
||||
} else if (entries.length) {
|
||||
log_info('Page');
|
||||
processPage({
|
||||
entries,
|
||||
title: 'Page',
|
||||
end: false,
|
||||
end : false,
|
||||
next,
|
||||
});
|
||||
entries = [];
|
||||
@@ -492,7 +520,7 @@ export default class LDAP {
|
||||
processPage({
|
||||
entries,
|
||||
title: 'Final Page',
|
||||
end: true,
|
||||
end : true,
|
||||
});
|
||||
entries = [];
|
||||
}
|
||||
@@ -547,7 +575,7 @@ export default class LDAP {
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.connected = false;
|
||||
this.connected = false;
|
||||
this.domainBinded = false;
|
||||
log_info('Disconecting');
|
||||
this.client.unbind();
|
||||
|
||||
@@ -41,28 +41,38 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
|
||||
let ldapUser;
|
||||
|
||||
try {
|
||||
ldap.connectSync();
|
||||
const users = ldap.searchUsersSync(loginRequest.username);
|
||||
|
||||
if (users.length !== 1) {
|
||||
log_info('Search returned', users.length, 'record(s) for', loginRequest.username);
|
||||
throw new Error('User not Found');
|
||||
}
|
||||
ldap.connectSync();
|
||||
|
||||
if (!!LDAP.settings_get('LDAP_USER_AUTHENTICATION')) {
|
||||
ldap.bindUserIfNecessary(loginRequest.username, loginRequest.ldapPass);
|
||||
ldapUser = ldap.searchUsersSync(loginRequest.username)[0];
|
||||
} else {
|
||||
|
||||
const users = ldap.searchUsersSync(loginRequest.username);
|
||||
|
||||
if (users.length !== 1) {
|
||||
log_info('Search returned', users.length, 'record(s) for', loginRequest.username);
|
||||
throw new Error('User not Found');
|
||||
}
|
||||
|
||||
if (ldap.authSync(users[0].dn, loginRequest.ldapPass) === true) {
|
||||
if (ldap.isUserInGroup(loginRequest.username, users[0])) {
|
||||
ldapUser = users[0];
|
||||
} else {
|
||||
throw new Error('User not in a valid group');
|
||||
}
|
||||
} else {
|
||||
log_info('Wrong password for', loginRequest.username);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ldap.authSync(users[0].dn, loginRequest.ldapPass) === true) {
|
||||
if (ldap.isUserInGroup(loginRequest.username, users[0])) {
|
||||
ldapUser = users[0];
|
||||
} else {
|
||||
throw new Error('User not in a valid group');
|
||||
}
|
||||
} else {
|
||||
log_info('Wrong password for', loginRequest.username);
|
||||
}
|
||||
} catch (error) {
|
||||
log_error(error);
|
||||
log_error(error);
|
||||
}
|
||||
|
||||
if (ldapUser === undefined) {
|
||||
if (!ldapUser) {
|
||||
if (LDAP.settings_get('LDAP_LOGIN_FALLBACK') === true) {
|
||||
return fallbackDefaultAccountSystem(self, loginRequest.username, loginRequest.ldapPass);
|
||||
}
|
||||
@@ -76,8 +86,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
|
||||
|
||||
const Unique_Identifier_Field = getLdapUserUniqueID(ldapUser);
|
||||
let user;
|
||||
|
||||
// Attempt to find user by unique identifier
|
||||
// Attempt to find user by unique identifier
|
||||
|
||||
if (Unique_Identifier_Field) {
|
||||
userQuery = {
|
||||
@@ -88,14 +97,14 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
|
||||
log_debug('userQuery', userQuery);
|
||||
|
||||
user = Meteor.users.findOne(userQuery);
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to find user by username
|
||||
|
||||
let username;
|
||||
let email;
|
||||
|
||||
if (LDAP.settings_get('LDAP_USERNAME_FIELD') !== '') {
|
||||
if (LDAP.settings_get('LDAP_USERNAME_FIELD') !== '') {
|
||||
username = slug(getLdapUsername(ldapUser));
|
||||
} else {
|
||||
username = slug(loginRequest.username);
|
||||
@@ -105,6 +114,7 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
|
||||
email = getLdapEmail(ldapUser);
|
||||
}
|
||||
|
||||
|
||||
if (!user) {
|
||||
if(email && LDAP.settings_get('LDAP_EMAIL_MATCH_REQUIRE') === true) {
|
||||
if(LDAP.settings_get('LDAP_EMAIL_MATCH_VERIFIED') === true) {
|
||||
@@ -169,6 +179,15 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
|
||||
},
|
||||
};
|
||||
|
||||
if (LDAP.settings_get('LDAP_SYNC_ADMIN_STATUS') === true) {
|
||||
log_debug('Updating admin status');
|
||||
const targetGroups = LDAP.settings_get('LDAP_SYNC_ADMIN_GROUPS').split(',');
|
||||
const groups = ldap.getUserGroups(username, ldapUser).filter((value) => targetGroups.includes(value));
|
||||
|
||||
user.isAdmin = groups.length > 0;
|
||||
Meteor.users.update({_id: user._id}, {$set: {isAdmin: user.isAdmin}});
|
||||
}
|
||||
|
||||
if( LDAP.settings_get('LDAP_SYNC_GROUP_ROLES') === true ) {
|
||||
log_debug('Updating Groups/Roles');
|
||||
const groups = ldap.getUserGroups(username, ldapUser);
|
||||
@@ -207,6 +226,15 @@ Accounts.registerLoginHandler('ldap', function(loginRequest) {
|
||||
|
||||
const result = addLdapUser(ldapUser, username, loginRequest.ldapPass);
|
||||
|
||||
if (LDAP.settings_get('LDAP_SYNC_ADMIN_STATUS') === true) {
|
||||
log_debug('Updating admin status');
|
||||
const targetGroups = LDAP.settings_get('LDAP_SYNC_ADMIN_GROUPS').split(',');
|
||||
const groups = ldap.getUserGroups(username, ldapUser).filter((value) => targetGroups.includes(value));
|
||||
|
||||
result.isAdmin = groups.length > 0;
|
||||
Meteor.users.update({_id: result.userId}, {$set: {isAdmin: result.isAdmin}});
|
||||
}
|
||||
|
||||
if( LDAP.settings_get('LDAP_SYNC_GROUP_ROLES') === true ) {
|
||||
const groups = ldap.getUserGroups(username, ldapUser);
|
||||
if( groups.length > 0 ) {
|
||||
|
||||
@@ -191,6 +191,8 @@
|
||||
# LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate
|
||||
# example : export LDAP_REJECT_UNAUTHORIZED=true
|
||||
#export LDAP_REJECT_UNAUTHORIZED=false
|
||||
# Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key).
|
||||
#export LDAP_USER_AUTHENTICATION=true
|
||||
# LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
|
||||
# example : export LDAP_USER_SEARCH_FILTER=
|
||||
#export LDAP_USER_SEARCH_FILTER=
|
||||
|
||||
@@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = (
|
||||
appTitle = (defaultText = "Wekan"),
|
||||
# The name of the app as it is displayed to the user.
|
||||
|
||||
appVersion = 268,
|
||||
appVersion = 276,
|
||||
# Increment this for every release.
|
||||
|
||||
appMarketingVersion = (defaultText = "2.66.0~2019-05-09"),
|
||||
appMarketingVersion = (defaultText = "2.74.0~2019-05-14"),
|
||||
# Human-readable presentation of the app version.
|
||||
|
||||
minUpgradableAppVersion = 0,
|
||||
|
||||
@@ -59,9 +59,12 @@ Meteor.publish('archivedBoards', function() {
|
||||
});
|
||||
});
|
||||
|
||||
Meteor.publishRelations('board', function(boardId) {
|
||||
// If isArchived = false, this will only return board elements which are not archived.
|
||||
// If isArchived = true, this will only return board elements which are archived.
|
||||
Meteor.publishRelations('board', function(boardId, isArchived) {
|
||||
this.unblock();
|
||||
check(boardId, String);
|
||||
check(isArchived, Boolean);
|
||||
const thisUserId = this.userId;
|
||||
|
||||
this.cursor(Boards.find({
|
||||
@@ -75,8 +78,8 @@ Meteor.publishRelations('board', function(boardId) {
|
||||
],
|
||||
// Sort required to ensure oplog usage
|
||||
}, { limit: 1, sort: { _id: 1 } }), function(boardId, board) {
|
||||
this.cursor(Lists.find({ boardId }));
|
||||
this.cursor(Swimlanes.find({ boardId }));
|
||||
this.cursor(Lists.find({ boardId, archived: isArchived }));
|
||||
this.cursor(Swimlanes.find({ boardId, archived: isArchived }));
|
||||
this.cursor(Integrations.find({ boardId }));
|
||||
this.cursor(CustomFields.find({ boardIds: {$in: [boardId]} }, { sort: { name: 1 } }));
|
||||
|
||||
@@ -115,8 +118,9 @@ Meteor.publishRelations('board', function(boardId) {
|
||||
parentCards.selector = (_ids) => ({ parentId: _ids });
|
||||
const boards = this.join(Boards);
|
||||
const subCards = this.join(Cards);
|
||||
subCards.selector = (_ids) => ({ archived: isArchived });
|
||||
|
||||
this.cursor(Cards.find({ boardId: {$in: [boardId, board.subtasksDefaultBoardId]}}), function(cardId, card) {
|
||||
this.cursor(Cards.find({ boardId: {$in: [boardId, board.subtasksDefaultBoardId]}, archived: isArchived }), function(cardId, card) {
|
||||
if (card.type === 'cardType-linkedCard') {
|
||||
const impCardId = card.linkedId;
|
||||
subCards.push(impCardId);
|
||||
|
||||
@@ -5,5 +5,5 @@ FastRender.onAllRoutes(function() {
|
||||
});
|
||||
|
||||
FastRender.route('/b/:id/:slug', function({ id }) {
|
||||
this.subscribe('board', id);
|
||||
this.subscribe('board', id, false);
|
||||
});
|
||||
+5
-1
@@ -3,7 +3,7 @@
|
||||
# All supported keys are defined here together with descriptions and default values
|
||||
|
||||
# list of supported keys
|
||||
keys="DEBUG 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 ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW EMAIL_NOTIFICATION_TIMEOUT CORS MATOMO_ADDRESS MATOMO_SITE_ID MATOMO_DO_NOT_TRACK MATOMO_WITH_USERNAME BROWSER_POLICY_ENABLED TRUSTED_URL WEBHOOKS_ATTRIBUTES OAUTH2_ENABLED OAUTH2_LOGIN_STYLE OAUTH2_CLIENT_ID OAUTH2_SECRET OAUTH2_SERVER_URL OAUTH2_AUTH_ENDPOINT OAUTH2_USERINFO_ENDPOINT OAUTH2_TOKEN_ENDPOINT OAUTH2_ID_MAP OAUTH2_USERNAME_MAP OAUTH2_FULLNAME_MAP OAUTH2_EMAIL_MAP 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_FULLNAME_FIELD LDAP_MERGE_EXISTING_USERS LDAP_SYNC_USER_DATA LDAP_SYNC_USER_DATA_FIELDMAP LDAP_SYNC_GROUP_ROLES LDAP_DEFAULT_DOMAIN LDAP_EMAIL_MATCH_ENABLE LDAP_EMAIL_MATCH_REQUIRE LDAP_EMAIL_MATCH_VERIFIED LDAP_EMAIL_FIELD LDAP_SYNC_ADMIN_STATUS LDAP_SYNC_ADMIN_GROUPS HEADER_LOGIN_ID HEADER_LOGIN_FIRSTNAME HEADER_LOGIN_LASTNAME HEADER_LOGIN_EMAIL LOGOUT_WITH_TIMER LOGOUT_IN LOGOUT_ON_HOURS LOGOUT_ON_MINUTES DEFAULT_AUTHENTICATION_METHOD"
|
||||
keys="DEBUG 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 ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW EMAIL_NOTIFICATION_TIMEOUT CORS MATOMO_ADDRESS MATOMO_SITE_ID MATOMO_DO_NOT_TRACK MATOMO_WITH_USERNAME BROWSER_POLICY_ENABLED TRUSTED_URL WEBHOOKS_ATTRIBUTES OAUTH2_ENABLED OAUTH2_LOGIN_STYLE OAUTH2_CLIENT_ID OAUTH2_SECRET OAUTH2_SERVER_URL OAUTH2_AUTH_ENDPOINT OAUTH2_USERINFO_ENDPOINT OAUTH2_TOKEN_ENDPOINT OAUTH2_ID_MAP OAUTH2_USERNAME_MAP OAUTH2_FULLNAME_MAP OAUTH2_EMAIL_MAP 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_AUTHENTICATION 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_FULLNAME_FIELD LDAP_MERGE_EXISTING_USERS LDAP_SYNC_USER_DATA LDAP_SYNC_USER_DATA_FIELDMAP LDAP_SYNC_GROUP_ROLES LDAP_DEFAULT_DOMAIN LDAP_EMAIL_MATCH_ENABLE LDAP_EMAIL_MATCH_REQUIRE LDAP_EMAIL_MATCH_VERIFIED LDAP_EMAIL_FIELD LDAP_SYNC_ADMIN_STATUS LDAP_SYNC_ADMIN_GROUPS HEADER_LOGIN_ID HEADER_LOGIN_FIRSTNAME HEADER_LOGIN_LASTNAME HEADER_LOGIN_EMAIL LOGOUT_WITH_TIMER LOGOUT_IN LOGOUT_ON_HOURS LOGOUT_ON_MINUTES DEFAULT_AUTHENTICATION_METHOD"
|
||||
|
||||
# default values
|
||||
DESCRIPTION_DEBUG="Debug OIDC OAuth2 etc. Example: sudo snap set wekan debug='true'"
|
||||
@@ -250,6 +250,10 @@ DESCRIPTION_LDAP_REJECT_UNAUTHORIZED="Reject Unauthorized Certificate"
|
||||
DEFAULT_LDAP_REJECT_UNAUTHORIZED="false"
|
||||
KEY_LDAP_REJECT_UNAUTHORIZED="ldap-reject-unauthorized"
|
||||
|
||||
DESCRIPTION_LDAP_USER_AUTHENTICATION="Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key)."
|
||||
DEFAULT_LDAP_USER_AUTHENTICATION="false"
|
||||
KEY_LDAP_USER_AUTHENTICATION="ldap-user-authentication"
|
||||
|
||||
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"
|
||||
|
||||
@@ -234,6 +234,9 @@ 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 "Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key)."
|
||||
echo -e "\t$ snap set $SNAP_NAME ldap-user-authentication='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=''"
|
||||
|
||||
@@ -173,6 +173,9 @@ REM # LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate
|
||||
REM # example : LDAP_REJECT_UNAUTHORIZED=true
|
||||
REM SET LDAP_REJECT_UNAUTHORIZED=false
|
||||
|
||||
REM # Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key).
|
||||
REM SET LDAP_USER_AUTHENTICATION=true
|
||||
|
||||
REM # LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
|
||||
REM # example : LDAP_USER_SEARCH_FILTER=
|
||||
REM SET LDAP_USER_SEARCH_FILTER=
|
||||
|
||||
@@ -210,6 +210,8 @@ function wekan_repo_check(){
|
||||
# LDAP_REJECT_UNAUTHORIZED : Reject Unauthorized Certificate
|
||||
# example : export LDAP_REJECT_UNAUTHORIZED=true
|
||||
#export LDAP_REJECT_UNAUTHORIZED=false
|
||||
# Option to login to the LDAP server with the user's own username and password, instead of an administrator key. Default: false (use administrator key).
|
||||
#export LDAP_USER_AUTHENTICATION=true
|
||||
# LDAP_USER_SEARCH_FILTER : Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed
|
||||
# example : export LDAP_USER_SEARCH_FILTER=
|
||||
#export LDAP_USER_SEARCH_FILTER=
|
||||
|
||||
Reference in new issue
Block a user