From 1dfb6ef477dba4d0faf4bf86026647f43fb2f5f6 Mon Sep 17 00:00:00 2001 From: lkisme Date: Fri, 24 Feb 2017 22:10:38 +0800 Subject: Admin panel: Only invited user can register in strict mode, Set mail server in admin panel, Switch strict mode in admin panel, Invite people to system in admin panel --- .eslintrc.json | 4 +- client/components/main/layouts.js | 2 + client/components/settings/invitationCode.jade | 5 + client/components/settings/invitationCode.js | 6 ++ client/components/settings/settingBody.jade | 72 ++++++++++++++ client/components/settings/settingBody.js | 126 +++++++++++++++++++++++++ client/components/settings/settingBody.styl | 112 ++++++++++++++++++++++ client/components/settings/settingHeader.jade | 21 +++++ client/components/settings/settingHeader.styl | 25 +++++ client/components/users/userHeader.jade | 2 + client/components/users/userHeader.js | 3 + config/accounts.js | 15 ++- config/router.js | 10 ++ i18n/en.i18n.json | 12 ++- i18n/zh-CN.i18n.json | 11 ++- models/invitationCodes.js | 45 +++++++++ models/settings.js | 111 ++++++++++++++++++++++ models/users.js | 45 ++++++++- server/publications/settings.js | 13 +++ server/publications/users.js | 8 ++ 20 files changed, 638 insertions(+), 10 deletions(-) create mode 100644 client/components/settings/invitationCode.jade create mode 100644 client/components/settings/invitationCode.js create mode 100644 client/components/settings/settingBody.jade create mode 100644 client/components/settings/settingBody.js create mode 100644 client/components/settings/settingBody.styl create mode 100644 client/components/settings/settingHeader.jade create mode 100644 client/components/settings/settingHeader.styl create mode 100644 models/invitationCodes.js create mode 100644 models/settings.js create mode 100644 server/publications/settings.js diff --git a/.eslintrc.json b/.eslintrc.json index 4808d873..0caa7a01 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -119,6 +119,8 @@ "allowIsBoardMember": true, "allowIsBoardMemberByCard": true, "Emoji": true, - "Checklists": true + "Checklists": true, + "Settings": true, + "InvitationCodes": true } } diff --git a/client/components/main/layouts.js b/client/components/main/layouts.js index 3df17f41..1e50b01a 100644 --- a/client/components/main/layouts.js +++ b/client/components/main/layouts.js @@ -1,4 +1,6 @@ Meteor.subscribe('boards'); +Meteor.subscribe('setting'); +Meteor.subscribe('user-admin'); BlazeLayout.setRoot('body'); diff --git a/client/components/settings/invitationCode.jade b/client/components/settings/invitationCode.jade new file mode 100644 index 00000000..171a2663 --- /dev/null +++ b/client/components/settings/invitationCode.jade @@ -0,0 +1,5 @@ +template(name='invitationCode') + .at-input#invitationcode + label(for='at-field-code') {{_ 'invitation-code'}} + + input#at-field-invitationcode(type="text" name='at-field-invitationcode' placeholder="{{_ 'invitation-code'}}") diff --git a/client/components/settings/invitationCode.js b/client/components/settings/invitationCode.js new file mode 100644 index 00000000..8143d5af --- /dev/null +++ b/client/components/settings/invitationCode.js @@ -0,0 +1,6 @@ +Template.invitationCode.onRendered(() => { + const strict = Settings.findOne().strict; + if(!strict){ + $('#invitationcode').hide(); + } +}); diff --git a/client/components/settings/settingBody.jade b/client/components/settings/settingBody.jade new file mode 100644 index 00000000..5d77bc60 --- /dev/null +++ b/client/components/settings/settingBody.jade @@ -0,0 +1,72 @@ +template(name="setting") + .setting-content + .content-title + span Settings + .content-body + .side-menu + ul + li.active + a.js-setting-menu(data-id="general-setting") System + li + a.js-setting-menu(data-id="email-setting") Email + .main-body + if loading.get + +spinner + else if generalSetting.get + +general + else if emailSetting.get + +email + +template(name="general") + ul#general-setting.setting-detail + li + a.flex.js-toggle-strict-mode + .materialCheckBox(class="{{#if currentSetting.strict}}is-checked{{/if}}") + + span Use Strict Mode + li + .invite-people(class="{{#if currentSetting.strict}}{{else}}hide{{/if}}") + ul + li + .title Invite People + textarea#email-to-invite.form-control(rows='5', placeholder="Email Adresses") + li + .title To board(s) + .bg-white + each boards + a.option.flex.js-toggle-board-choose(id= _id) + .materialCheckBox(data-id= _id) + + span= title + + li + button.js-email-invite.primary Invite + +template(name='email') + ul#email-setting.setting-detail + li.smtp-form + .title SMTP Host {{currentSetting.mailServer.port}} + .description The address of the SMTP server that handles your emails. + .form-group + input.form-control#mail-server-host(type="text", placeholder="smtp.domain.com" value="{{currentSetting.mailServer.host}}") + li.smtp-form + .title SMTP Port + .description The port your SMTP server uses for outgoing emails. + .form-group + input.form-control#mail-server-port(type="text", placeholder="25" value="{{currentSetting.mailServer.port}}") + li.smtp-form + .title SMTP user name + .form-group + input.form-control#mail-server-username(type="text", placeholder="user name" value="{{currentSetting.mailServer.username}}") + li.smtp-form + .title SMTP password + .form-group + input.form-control#mail-server-password(type="text", placeholder="password" value="{{currentSetting.mailServer.password}}") + li.smtp-form + .title From + .Email address you want to use to send emails. + .form-group + input.form-control#mail-server-from(type="email", placeholder="no-reply@domain.com" value="{{currentSetting.mailServer.from}}") + + li + button.js-save.primary Save diff --git a/client/components/settings/settingBody.js b/client/components/settings/settingBody.js new file mode 100644 index 00000000..047bbd1c --- /dev/null +++ b/client/components/settings/settingBody.js @@ -0,0 +1,126 @@ +Meteor.subscribe('setting'); +Meteor.subscribe('mailServer'); + +BlazeComponent.extendComponent({ + onCreated() { + this.error = new ReactiveVar(''); + this.loading = new ReactiveVar(false); + this.generalSetting = new ReactiveVar(true); + this.emailSetting = new ReactiveVar(false); + }, + + setError(error) { + this.error.set(error); + }, + + setLoading(w) { + this.loading.set(w); + }, + + checkField(selector) { + const value = $(selector).val(); + if(!value || value.trim() === ''){ + $(selector).parents('li.smtp-form').addClass('has-error'); + throw Error('blank field'); + } else { + return value; + } + }, + + currentSetting(){ + return Settings.findOne(); + }, + + boards() { + return Boards.find({ + archived: false, + 'members.userId': Meteor.userId(), + 'members.isAdmin': true, + }, { + sort: ['title'], + }); + }, + toggleStrictMode(){ + this.setLoading(true); + const isStrictMode = this.currentSetting().strict; + Settings.update(Settings.findOne()._id, {$set:{strict: !isStrictMode}}); + this.setLoading(false); + if(isStrictMode){ + $('.invite-people').slideUp(); + }else{ + $('.invite-people').slideDown(); + } + }, + + switchMenu(event){ + const target = $(event.target); + if(!target.hasClass('active')){ + $('.side-menu li.active').removeClass('active'); + target.parent().addClass('active'); + const targetID = target.data('id'); + this.generalSetting.set('general-setting' === targetID); + this.emailSetting.set('email-setting' === targetID); + } + }, + + checkBoard(event){ + let target = $(event.target); + if(!target.hasClass('js-toggle-board-choose')){ + target = target.parent(); + } + const checkboxId = target.attr('id'); + $(`#${checkboxId} .materialCheckBox`).toggleClass('is-checked'); + $(`#${checkboxId}`).toggleClass('is-checked'); + }, + + inviteThroughEmail(){ + this.setLoading(true); + const emails = $('#email-to-invite').val().trim().split('\n').join(',').split(','); + const boardsToInvite = []; + $('.js-toggle-board-choose .materialCheckBox.is-checked').each(function () { + boardsToInvite.push($(this).data('id')); + }); + const validEmails = []; + emails.forEach((email) => { + if (email && SimpleSchema.RegEx.Email.test(email.trim())) { + validEmails.push(email.trim()); + } + }); + Meteor.call('sendInvitation', validEmails, boardsToInvite, () => { + // if (!err) { + // TODO - show more info to user + // } + this.setLoading(false); + }); + }, + + saveMailServerInfo(){ + this.setLoading(true); + $('li').removeClass('has-error'); + + try{ + const host = this.checkField('#mail-server-host'); + const port = this.checkField('#mail-server-port'); + const username = this.checkField('#mail-server-username'); + const password = this.checkField('#mail-server-password'); + const from = this.checkField('#mail-server-from'); + Settings.update(Settings.findOne()._id, {$set:{'mailServer.host':host, 'mailServer.port': port, 'mailServer.username': username, + 'mailServer.password': password, 'mailServer.from': from}}); + } catch (e) { + return; + } finally { + this.setLoading(false); + } + + }, + + events(){ + return [{ + 'click a.js-toggle-strict-mode': this.toggleStrictMode, + 'click a.js-setting-menu': this.switchMenu, + 'click a.js-toggle-board-choose': this.checkBoard, + 'click button.js-email-invite': this.inviteThroughEmail, + 'click button.js-save': this.saveMailServerInfo, + }]; + }, +}).register('setting'); diff --git a/client/components/settings/settingBody.styl b/client/components/settings/settingBody.styl new file mode 100644 index 00000000..118d364c --- /dev/null +++ b/client/components/settings/settingBody.styl @@ -0,0 +1,112 @@ +.flex + display: -webkit-box + display: -moz-box + display: -webkit-flex + display: -moz-flex + display: -ms-flexbox + display: flex + +.setting-content + padding 30px + color: #727479 + background: #dedede + width 100% + height 100% + position: absolute; + + .content-title + font-size 20px + + .content-body + display flex + padding-top 15px + height 100% + + .side-menu + background-color: #f7f7f7; + border: 1px solid #f0f0f0; + border-radius: 4px; + width: 250px; + box-shadow: inset -1px -1px 3px rgba(0,0,0,.05); + + ul + + li + margin: 0.1rem 0.2rem; + + &.active + background #fff + box-shadow 0 1px 2px rgba(0,0,0,0.15); + + &:hover + background #fff + box-shadow 0 1px 2px rgba(0,0,0,0.15); + a + @extends .flex + padding: 1rem 0 1rem 1rem + width: 100% - 5rem + + + span + font-size: 13px + + .main-body + padding: 0.1em 1em + + ul + li + padding: 0.5rem 0.5rem; + + a + .is-checked + border-bottom: 2px solid #2980b9; + border-right: 2px solid #2980b9; + + span + padding: 0 0.5rem + + .invite-people + padding-left 20px; + li + min-width: 500px; + + ul.no-margin-bottom + margin-bottom: 0; + + .bg-white + a + background #f7f7f7 + &.is-checked + background #fff + + +.option + @extends .flex + -webkit-border-radius: 3px; + border-radius: 3px; + background: #fff; + text-decoration: none; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.2); + box-shadow: 0 1px 2px rgba(0,0,0,0.2); + margin-top: 5px; + padding: 5px; + +.title + font-weight 700; + margin-bottom 0.5rem; +.description + margin-bottom 0.5rem; +.bg-white + background #f9fbfc; + +.form-control.has-error + border-color: #a94442; + box-shadow: inset 0 1px 1px rgba(0,0,0,.075); + +li.has-error + color #a94442 + .form-group + .form-control + border-color: #a94442; + box-shadow: inset 0 1px 1px rgba(0,0,0,.075); + diff --git a/client/components/settings/settingHeader.jade b/client/components/settings/settingHeader.jade new file mode 100644 index 00000000..01873eae --- /dev/null +++ b/client/components/settings/settingHeader.jade @@ -0,0 +1,21 @@ +template(name="settingHeaderBar") + h1.header-setting-menu + span {{_ 'admin-panel'}} + + .setting-header-btns.left + unless isMiniScreen + unless isSandstorm + if currentUser + a.setting-header-btn.settings.active + i.fa(class="fa-cog") + span {{_ 'option-setting'}} +//TODO +// a.setting-header-btn.people +// i.fa(class="fa-users") +// span {{_ 'option-people'}} + + else + a.setting-header-btn.js-log-in( + title="{{_ 'log-in'}}") + i.fa.fa-sign-in + span {{_ 'log-in'}} diff --git a/client/components/settings/settingHeader.styl b/client/components/settings/settingHeader.styl new file mode 100644 index 00000000..995ed26d --- /dev/null +++ b/client/components/settings/settingHeader.styl @@ -0,0 +1,25 @@ +#header #header-main-bar .setting-header-btn + &.active, + &:hover:not(.is-disabled) + background: rgba(0, 0, 0, .15) + color: darken(white, 5%) + margin-left: 20px; + padding-right: 10px; + height: 28px; + font-size: 13px; + float: left; + overflow: hidden; + line-height: @height; + margin: 0 2px; + + i.fa + float: left + display: block + line-height: 28px + color: darken(white, 5%) + margin: 0 10px + + + span + display: inline-block + margin-top: 1px + margin-right: 10px \ No newline at end of file diff --git a/client/components/users/userHeader.jade b/client/components/users/userHeader.jade index ad41e8aa..51b0888b 100644 --- a/client/components/users/userHeader.jade +++ b/client/components/users/userHeader.jade @@ -17,6 +17,8 @@ template(name="memberMenuPopup") li: a.js-change-password {{_ 'changePasswordPopup-title'}} li: a.js-change-language {{_ 'changeLanguagePopup-title'}} li: a.js-edit-notification {{_ 'editNotificationPopup-title'}} + if currentUser.isAdmin + li: a.js-go-setting(href='/setting') {{_ 'admin-panel'}} hr ul.pop-over-list li: a.js-logout {{_ 'log-out'}} diff --git a/client/components/users/userHeader.js b/client/components/users/userHeader.js index 98053ed1..73a11fc0 100644 --- a/client/components/users/userHeader.js +++ b/client/components/users/userHeader.js @@ -15,6 +15,9 @@ Template.memberMenuPopup.events({ AccountsTemplates.logout(); }, + 'click .js-go-setting'() { + Popup.close(); + }, }); Template.editProfilePopup.events({ diff --git a/config/accounts.js b/config/accounts.js index 9ab26b33..51c0f49e 100644 --- a/config/accounts.js +++ b/config/accounts.js @@ -1,12 +1,21 @@ const passwordField = AccountsTemplates.removeField('password'); const emailField = AccountsTemplates.removeField('email'); + AccountsTemplates.addFields([{ _id: 'username', type: 'text', displayName: 'username', required: true, minLength: 2, -}, emailField, passwordField]); +}, emailField, passwordField, { + _id: 'invitationcode', + type: 'text', + displayName: 'Invitation Code', + required: false, + minLength: 6, + errStr: 'Invitation code doesn\'t exist', + template: 'invitationCode', +}]); AccountsTemplates.configure({ defaultLayout: 'userFormsLayout', @@ -48,9 +57,6 @@ AccountsTemplates.configureRoute('changePwd', { }); if (Meteor.isServer) { - if (process.env.MAIL_FROM) { - Accounts.emailTemplates.from = process.env.MAIL_FROM; - } ['resetPassword-subject', 'resetPassword-text', 'verifyEmail-subject', 'verifyEmail-text', 'enrollAccount-subject', 'enrollAccount-text'].forEach((str) => { const [templateName, field] = str.split('-'); @@ -63,3 +69,4 @@ if (Meteor.isServer) { }; }); } + diff --git a/config/router.js b/config/router.js index 7194621b..f136f8cc 100644 --- a/config/router.js +++ b/config/router.js @@ -99,6 +99,16 @@ FlowRouter.route('/import', { }, }); +FlowRouter.route('/setting', { + name: 'setting', + action() { + BlazeLayout.render('defaultLayout', { + headerBar: 'settingHeaderBar', + content: 'setting', + }); + }, +}); + FlowRouter.notFound = { action() { BlazeLayout.render('defaultLayout', { content: 'notFound' }); diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index ad74a0e0..b5c1b55e 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -323,5 +323,13 @@ "welcome-board": "Welcome Board", "welcome-list1": "Basics", "welcome-list2": "Advanced", - "what-to-do": "What do you want to do?" -} \ No newline at end of file + "what-to-do": "What do you want to do?", + "admin-panel": "Admin Panel", + "system-setting": "System Setting", + "option-setting": "Settings", + "option-people": "People", + "invitation-code": "Invitation Code", + "email-invite-register-subject": "__inviter__ sent you an invitation", + "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to Wekan for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.\n", + "error-invitation-code-not-exist": "Invitation code doesn't exist" +} diff --git a/i18n/zh-CN.i18n.json b/i18n/zh-CN.i18n.json index 859cb2b7..c6bbf7f9 100644 --- a/i18n/zh-CN.i18n.json +++ b/i18n/zh-CN.i18n.json @@ -322,5 +322,12 @@ "welcome-board": "“欢迎”看板", "welcome-list1": "基本", "welcome-list2": "高阶", - "what-to-do": "要做什么?" -} \ No newline at end of file + "what-to-do": "要做什么?", + "system-setting": "系统设置", + "option-setting": "设置", + "option-people": "成员", + "invitation-code": "邀请码", + "email-invite-register-subject": "__inviter__ 向您发出邀请", + "email-invite-register-text": "尊敬的 __user__,\n\n__inviter__ 邀请您加入看板参与协作。\n\n请点击下面的链接访问进行注册:\n\n__url__\n您的邀请码是: __icode__\n\n谢谢。\n", + "error-invitation-code-not-exist": "验证码不存在" +} diff --git a/models/invitationCodes.js b/models/invitationCodes.js new file mode 100644 index 00000000..5761977a --- /dev/null +++ b/models/invitationCodes.js @@ -0,0 +1,45 @@ +InvitationCodes = new Mongo.Collection('invitation_codes'); + +InvitationCodes.attachSchema(new SimpleSchema({ + code: { + type: String, + }, + email: { + type: String, + unique: true, + regEx: SimpleSchema.RegEx.Email, + }, + createdAt: { + type: Date, + denyUpdate: false, + }, + // always be the admin if only one admin + authorId: { + type: String, + }, + boardsToBeInvited: { + type: [String], + optional: true, + }, + valid: { + type: Boolean, + defaultValue: true, + }, +})); + +InvitationCodes.helpers({ + author(){ + return Users.findOne(this.authorId); + }, +}); + +// InvitationCodes.before.insert((userId, doc) => { + // doc.createdAt = new Date(); + // doc.authorId = userId; +// }); + +if (Meteor.isServer) { + Boards.deny({ + fetch: ['members'], + }); +} diff --git a/models/settings.js b/models/settings.js new file mode 100644 index 00000000..77bf8d24 --- /dev/null +++ b/models/settings.js @@ -0,0 +1,111 @@ +Settings = new Mongo.Collection('settings'); + +Settings.attachSchema(new SimpleSchema({ + strict: { + type: Boolean, + }, + 'mailServer.username': { + type: String, + }, + 'mailServer.password': { + type: String, + }, + 'mailServer.host': { + type: String, + }, + 'mailServer.port': { + type: String, + }, + 'mailServer.from': { + type: String, + defaultValue: 'Kanban', + }, + createdAt: { + type: Date, + denyUpdate: true, + }, + modifiedAt: { + type: Date, + }, +})); +Settings.helpers({ + mailUrl () { + const mailUrl = `smtp://${this.mailServer.username}:${this.mailServer.password}@${this.mailServer.host}:${this.mailServer.port}/`; + return mailUrl; + }, +}); +Settings.allow({ + update(userId) { + const user = Users.findOne(userId); + return user && user.isAdmin; + }, +}); + +Settings.before.update((userId, doc, fieldNames, modifier) => { + modifier.$set = modifier.$set || {}; + modifier.$set.modifiedAt = new Date(); +}); + +if (Meteor.isServer) { + Meteor.startup(() => { + const setting = Settings.findOne({}); + if(!setting){ + const now = new Date(); + const defaultSetting = {strict: false, mailServer: { + username: '', password:'', host: '', port:'', from: '', + }, createdAt: now, modifiedAt: now}; + Settings.insert(defaultSetting); + } + const newSetting = Settings.findOne(); + process.env.MAIL_URL = newSetting.mailUrl(); + Accounts.emailTemplates.from = newSetting.mailServer.from; + }); + + function getRandomNum (min, max) { + const range = max - min; + const rand = Math.random(); + return (min + Math.round(rand * range)); + } + + function sendInvitationEmail (_id){ + const icode = InvitationCodes.findOne(_id); + const author = Users.findOne(Meteor.userId()); + try { + const params = { + email: icode.email, + inviter: Users.findOne(icode.authorId).username, + user: icode.email.split('@')[0], + icode: icode.code, + url: FlowRouter.url('sign-up'), + }; + const lang = author.getLanguage(); + Email.send({ + to: icode.email, + from: Accounts.emailTemplates.from, + subject: TAPi18n.__('email-invite-register-subject', params, lang), + text: TAPi18n.__('email-invite-register-text', params, lang), + }); + } catch (e) { + throw new Meteor.Error('email-fail', e.message); + } + } + + Meteor.methods({ + sendInvitation(emails, boards) { + check(emails, [String]); + check(boards, [String]); + const user = Users.findOne(Meteor.userId()); + if(!user.isAdmin){ + throw new Meteor.Error('not-allowed'); + } + emails.forEach((email) => { + if (email && SimpleSchema.RegEx.Email.test(email)) { + const code = getRandomNum(100000, 999999); + InvitationCodes.insert({code, email, boardsToBeInvited: boards, createdAt: new Date(), authorId: Meteor.userId()}, function(err, _id){ + if(!err && _id) sendInvitationEmail(_id); + }); + } + }); + }, + }); +} diff --git a/models/users.js b/models/users.js index 58513231..c7db8fff 100644 --- a/models/users.js +++ b/models/users.js @@ -348,7 +348,7 @@ if (Meteor.isServer) { if (user._id === inviter._id) throw new Meteor.Error('error-user-notAllowSelf'); } else { if (posAt <= 0) throw new Meteor.Error('error-user-doesNotExist'); - + if (Settings.findOne().strict) throw new Meteor.Error('error-user-notCreated'); const email = username; username = email.substring(0, posAt); const newUserId = Accounts.createUser({ username, email }); @@ -389,6 +389,28 @@ if (Meteor.isServer) { return { username: user.username, email: user.emails[0].address }; }, }); + Accounts.onCreateUser((options, user) => { + const userCount = Users.find().count(); + if (userCount === 0){ + user.isAdmin = true; + return user; + } + const strict = Settings.findOne().strict; + if (!strict) { + return user; + } + + const iCode = options.profile.invitationcode | ''; + + const invitationCode = InvitationCodes.findOne({code: iCode, valid:true}); + if (!invitationCode) { + throw new Meteor.Error('error-invitation-code-not-exist'); + }else{ + user.profile = {icode: options.profile.invitationcode}; + } + + return user; + }); } if (Meteor.isServer) { @@ -458,4 +480,25 @@ if (Meteor.isServer) { }); }); } + + Users.after.insert((userId, doc) => { + + //invite user to corresponding boards + const strict = Settings.findOne().strict; + if (strict) { + const user = Users.findOne(doc._id); + const invitationCode = InvitationCodes.findOne({code: user.profile.icode, valid:true}); + if (!invitationCode) { + throw new Meteor.Error('error-user-notCreated'); + }else{ + invitationCode.boardsToBeInvited.forEach((boardId) => { + const board = Boards.findOne(boardId); + board.addMember(doc._id); + }); + user.profile = {invitedBoards: invitationCode.boardsToBeInvited}; + InvitationCodes.update(invitationCode._id, {$set: {valid:false}}); + } + } + }); } + diff --git a/server/publications/settings.js b/server/publications/settings.js new file mode 100644 index 00000000..e36189e1 --- /dev/null +++ b/server/publications/settings.js @@ -0,0 +1,13 @@ +Meteor.publish('setting', () => { + return Settings.find({}, {fields:{strict: 1}}); +}); + +Meteor.publish('mailServer', function () { + if (!Match.test(this.userId, String)) + return []; + const user = Users.findOne(this.userId); + if(user && user.isAdmin){ + return Settings.find({}, {fields: {mailServer: 1}}); + } + return []; +}); diff --git a/server/publications/users.js b/server/publications/users.js index 4321e32b..4fd98e13 100644 --- a/server/publications/users.js +++ b/server/publications/users.js @@ -9,3 +9,11 @@ Meteor.publish('user-miniprofile', function(userId) { }, }); }); + +Meteor.publish('user-admin', function() { + return Meteor.users.find(this.userId, { + fields: { + isAdmin: 1, + }, + }); +}); -- cgit v1.2.3-1-g7c22 From e441e751d5e58fcf75758f3c47746f627e18dc55 Mon Sep 17 00:00:00 2001 From: lkisme Date: Sat, 25 Feb 2017 11:29:37 +0800 Subject: remove translation in zh-CN --- i18n/zh-CN.i18n.json | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/i18n/zh-CN.i18n.json b/i18n/zh-CN.i18n.json index c6bbf7f9..859cb2b7 100644 --- a/i18n/zh-CN.i18n.json +++ b/i18n/zh-CN.i18n.json @@ -322,12 +322,5 @@ "welcome-board": "“欢迎”看板", "welcome-list1": "基本", "welcome-list2": "高阶", - "what-to-do": "要做什么?", - "system-setting": "系统设置", - "option-setting": "设置", - "option-people": "成员", - "invitation-code": "邀请码", - "email-invite-register-subject": "__inviter__ 向您发出邀请", - "email-invite-register-text": "尊敬的 __user__,\n\n__inviter__ 邀请您加入看板参与协作。\n\n请点击下面的链接访问进行注册:\n\n__url__\n您的邀请码是: __icode__\n\n谢谢。\n", - "error-invitation-code-not-exist": "验证码不存在" -} + "what-to-do": "要做什么?" +} \ No newline at end of file -- cgit v1.2.3-1-g7c22 From e3c3cc0d8df14c84852a854383efa1b4bb83f218 Mon Sep 17 00:00:00 2001 From: lkisme Date: Sun, 26 Feb 2017 14:25:29 +0800 Subject: Make mailServer setting optional --- models/settings.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/models/settings.js b/models/settings.js index 77bf8d24..b160c2a4 100644 --- a/models/settings.js +++ b/models/settings.js @@ -6,18 +6,23 @@ Settings.attachSchema(new SimpleSchema({ }, 'mailServer.username': { type: String, + optional: true, }, 'mailServer.password': { type: String, + optional: true, }, 'mailServer.host': { type: String, + optional: true, }, 'mailServer.port': { type: String, + optional: true, }, 'mailServer.from': { type: String, + optional: true, defaultValue: 'Kanban', }, createdAt: { -- cgit v1.2.3-1-g7c22 From 39f2837838ba30ec02bfe9f33c9fa0dfca05d1a6 Mon Sep 17 00:00:00 2001 From: lkisme Date: Sun, 26 Feb 2017 21:11:15 +0800 Subject: wording change, email sending optimization, add texts to i18n --- client/components/settings/invitationCode.js | 4 +-- client/components/settings/settingBody.jade | 43 +++++++++++++-------------- client/components/settings/settingBody.js | 28 +++++++++-------- client/components/settings/settingHeader.jade | 4 +-- config/router.js | 10 +++++++ i18n/en.i18n.json | 17 +++++++++-- models/settings.js | 6 ++-- models/users.js | 10 +++---- server/publications/settings.js | 2 +- 9 files changed, 74 insertions(+), 50 deletions(-) diff --git a/client/components/settings/invitationCode.js b/client/components/settings/invitationCode.js index 8143d5af..e712c89a 100644 --- a/client/components/settings/invitationCode.js +++ b/client/components/settings/invitationCode.js @@ -1,6 +1,6 @@ Template.invitationCode.onRendered(() => { - const strict = Settings.findOne().strict; - if(!strict){ + const disableRegistration = Settings.findOne().disableRegistration; + if(!disableRegistration){ $('#invitationcode').hide(); } }); diff --git a/client/components/settings/settingBody.jade b/client/components/settings/settingBody.jade index 5d77bc60..fdab3173 100644 --- a/client/components/settings/settingBody.jade +++ b/client/components/settings/settingBody.jade @@ -1,14 +1,14 @@ template(name="setting") .setting-content .content-title - span Settings + span {{_ 'settings'}} .content-body .side-menu ul li.active - a.js-setting-menu(data-id="general-setting") System + a.js-setting-menu(data-id="registration-setting") {{_ 'registration'}} li - a.js-setting-menu(data-id="email-setting") Email + a.js-setting-menu(data-id="email-setting") {{_ 'email'}} .main-body if loading.get +spinner @@ -18,20 +18,20 @@ template(name="setting") +email template(name="general") - ul#general-setting.setting-detail + ul#registration-setting.setting-detail li - a.flex.js-toggle-strict-mode - .materialCheckBox(class="{{#if currentSetting.strict}}is-checked{{/if}}") + a.flex.js-toggle-registration + .materialCheckBox(class="{{#if currentSetting.disableRegistration}}is-checked{{/if}}") - span Use Strict Mode + span {{_ 'disable-self-registration'}} li - .invite-people(class="{{#if currentSetting.strict}}{{else}}hide{{/if}}") + .invite-people(class="{{#if currentSetting.disableRegistration}}{{else}}hide{{/if}}") ul li - .title Invite People - textarea#email-to-invite.form-control(rows='5', placeholder="Email Adresses") + .title {{_ 'invite-people'}} + textarea#email-to-invite.form-control(rows='5', placeholder="{{_ 'email-addresses'}}") li - .title To board(s) + .title {{_ 'to-boards'}} .bg-white each boards a.option.flex.js-toggle-board-choose(id= _id) @@ -40,31 +40,30 @@ template(name="general") span= title li - button.js-email-invite.primary Invite + button.js-email-invite.primary {{_ 'invite'}} template(name='email') ul#email-setting.setting-detail li.smtp-form - .title SMTP Host {{currentSetting.mailServer.port}} - .description The address of the SMTP server that handles your emails. + .title {{_ 'smtp-host'}} + .description {{_ 'smtp-host-description'}} .form-group input.form-control#mail-server-host(type="text", placeholder="smtp.domain.com" value="{{currentSetting.mailServer.host}}") li.smtp-form - .title SMTP Port - .description The port your SMTP server uses for outgoing emails. + .title {{_ 'smtp-port'}} + .description {{_ 'smtp-port-description'}} .form-group input.form-control#mail-server-port(type="text", placeholder="25" value="{{currentSetting.mailServer.port}}") li.smtp-form - .title SMTP user name + .title {{_ 'smtp-username'}} .form-group - input.form-control#mail-server-username(type="text", placeholder="user name" value="{{currentSetting.mailServer.username}}") + input.form-control#mail-server-username(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}") li.smtp-form - .title SMTP password + .title {{_ 'smtp-password'}} .form-group - input.form-control#mail-server-password(type="text", placeholder="password" value="{{currentSetting.mailServer.password}}") + input.form-control#mail-server-password(type="text", placeholder="{{_ 'password'}}" value="{{currentSetting.mailServer.password}}") li.smtp-form - .title From - .Email address you want to use to send emails. + .title {{_ 'send-from'}} .form-group input.form-control#mail-server-from(type="email", placeholder="no-reply@domain.com" value="{{currentSetting.mailServer.from}}") diff --git a/client/components/settings/settingBody.js b/client/components/settings/settingBody.js index 047bbd1c..5ae982f7 100644 --- a/client/components/settings/settingBody.js +++ b/client/components/settings/settingBody.js @@ -40,12 +40,12 @@ BlazeComponent.extendComponent({ sort: ['title'], }); }, - toggleStrictMode(){ + toggleRegistration(){ this.setLoading(true); - const isStrictMode = this.currentSetting().strict; - Settings.update(Settings.findOne()._id, {$set:{strict: !isStrictMode}}); + const registrationClosed = this.currentSetting().disableRegistration; + Settings.update(Settings.findOne()._id, {$set:{disableRegistration: !registrationClosed}}); this.setLoading(false); - if(isStrictMode){ + if(registrationClosed){ $('.invite-people').slideUp(); }else{ $('.invite-people').slideDown(); @@ -58,7 +58,7 @@ BlazeComponent.extendComponent({ $('.side-menu li.active').removeClass('active'); target.parent().addClass('active'); const targetID = target.data('id'); - this.generalSetting.set('general-setting' === targetID); + this.generalSetting.set('registration-setting' === targetID); this.emailSetting.set('email-setting' === targetID); } }, @@ -74,7 +74,6 @@ BlazeComponent.extendComponent({ }, inviteThroughEmail(){ - this.setLoading(true); const emails = $('#email-to-invite').val().trim().split('\n').join(',').split(','); const boardsToInvite = []; $('.js-toggle-board-choose .materialCheckBox.is-checked').each(function () { @@ -86,12 +85,15 @@ BlazeComponent.extendComponent({ validEmails.push(email.trim()); } }); - Meteor.call('sendInvitation', validEmails, boardsToInvite, () => { - // if (!err) { - // TODO - show more info to user - // } - this.setLoading(false); - }); + if (validEmails.length) { + this.setLoading(true); + Meteor.call('sendInvitation', validEmails, boardsToInvite, () => { + // if (!err) { + // TODO - show more info to user + // } + this.setLoading(false); + }); + } }, saveMailServerInfo(){ @@ -116,7 +118,7 @@ BlazeComponent.extendComponent({ events(){ return [{ - 'click a.js-toggle-strict-mode': this.toggleStrictMode, + 'click a.js-toggle-registration': this.toggleRegistration, 'click a.js-setting-menu': this.switchMenu, 'click a.js-toggle-board-choose': this.checkBoard, 'click button.js-email-invite': this.inviteThroughEmail, diff --git a/client/components/settings/settingHeader.jade b/client/components/settings/settingHeader.jade index 01873eae..fb884056 100644 --- a/client/components/settings/settingHeader.jade +++ b/client/components/settings/settingHeader.jade @@ -8,11 +8,11 @@ template(name="settingHeaderBar") if currentUser a.setting-header-btn.settings.active i.fa(class="fa-cog") - span {{_ 'option-setting'}} + span {{_ 'settings'}} //TODO // a.setting-header-btn.people // i.fa(class="fa-users") -// span {{_ 'option-people'}} +// span {{_ 'people'}} else a.setting-header-btn.js-log-in( diff --git a/config/router.js b/config/router.js index f136f8cc..72592bd6 100644 --- a/config/router.js +++ b/config/router.js @@ -101,6 +101,16 @@ FlowRouter.route('/import', { FlowRouter.route('/setting', { name: 'setting', + triggersEnter: [ + AccountsTemplates.ensureSignedIn, + () => { + Session.set('currentBoard', null); + Session.set('currentCard', null); + + Filter.reset(); + EscapeActions.executeAll(); + }, + ], action() { BlazeLayout.render('defaultLayout', { headerBar: 'settingHeaderBar', diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index b5c1b55e..c57d9274 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -326,8 +326,21 @@ "what-to-do": "What do you want to do?", "admin-panel": "Admin Panel", "system-setting": "System Setting", - "option-setting": "Settings", - "option-people": "People", + "settings": "Settings", + "people": "People", + "registration": "Registration", + "disable-self-registration": "Disable Self-Registration", + "invite": "Invite", + "invite-people": "Invite People", + "to-boards": "To board(s)", + "email-addresses":"Email Addresses", + "smtp-host-description": "The address of the SMTP server that handles your emails.", + "smtp-port-description": "The port your SMTP server uses for outgoing emails.", + "smtp-host": "SMTP Host", + "smtp-port": "SMTP Port", + "smtp-username": "Username", + "smtp-password": "Password", + "send-from": "From", "invitation-code": "Invitation Code", "email-invite-register-subject": "__inviter__ sent you an invitation", "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to Wekan for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.\n", diff --git a/models/settings.js b/models/settings.js index b160c2a4..b9ff1b37 100644 --- a/models/settings.js +++ b/models/settings.js @@ -1,7 +1,7 @@ Settings = new Mongo.Collection('settings'); Settings.attachSchema(new SimpleSchema({ - strict: { + disableRegistration: { type: Boolean, }, 'mailServer.username': { @@ -23,7 +23,7 @@ Settings.attachSchema(new SimpleSchema({ 'mailServer.from': { type: String, optional: true, - defaultValue: 'Kanban', + defaultValue: 'Wekan', }, createdAt: { type: Date, @@ -56,7 +56,7 @@ if (Meteor.isServer) { const setting = Settings.findOne({}); if(!setting){ const now = new Date(); - const defaultSetting = {strict: false, mailServer: { + const defaultSetting = {disableRegistration: false, mailServer: { username: '', password:'', host: '', port:'', from: '', }, createdAt: now, modifiedAt: now}; Settings.insert(defaultSetting); diff --git a/models/users.js b/models/users.js index c7db8fff..b77c7a84 100644 --- a/models/users.js +++ b/models/users.js @@ -348,7 +348,7 @@ if (Meteor.isServer) { if (user._id === inviter._id) throw new Meteor.Error('error-user-notAllowSelf'); } else { if (posAt <= 0) throw new Meteor.Error('error-user-doesNotExist'); - if (Settings.findOne().strict) throw new Meteor.Error('error-user-notCreated'); + if (Settings.findOne().disableRegistration) throw new Meteor.Error('error-user-notCreated'); const email = username; username = email.substring(0, posAt); const newUserId = Accounts.createUser({ username, email }); @@ -395,8 +395,8 @@ if (Meteor.isServer) { user.isAdmin = true; return user; } - const strict = Settings.findOne().strict; - if (!strict) { + const disableRegistration = Settings.findOne().disableRegistration; + if (!disableRegistration) { return user; } @@ -484,8 +484,8 @@ if (Meteor.isServer) { Users.after.insert((userId, doc) => { //invite user to corresponding boards - const strict = Settings.findOne().strict; - if (strict) { + const disableRegistration = Settings.findOne().disableRegistration; + if (disableRegistration) { const user = Users.findOne(doc._id); const invitationCode = InvitationCodes.findOne({code: user.profile.icode, valid:true}); if (!invitationCode) { diff --git a/server/publications/settings.js b/server/publications/settings.js index e36189e1..c2d9fdff 100644 --- a/server/publications/settings.js +++ b/server/publications/settings.js @@ -1,5 +1,5 @@ Meteor.publish('setting', () => { - return Settings.find({}, {fields:{strict: 1}}); + return Settings.find({}, {fields:{disableRegistration: 1}}); }); Meteor.publish('mailServer', function () { -- cgit v1.2.3-1-g7c22