mirror of https://github.com/wekan/wekan
commit
7c9a30d8fe
@ -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'}}") |
@ -0,0 +1,6 @@ |
||||
Template.invitationCode.onRendered(() => { |
||||
const disableRegistration = Settings.findOne().disableRegistration; |
||||
if(!disableRegistration){ |
||||
$('#invitationcode').hide(); |
||||
} |
||||
}); |
@ -0,0 +1,71 @@ |
||||
template(name="setting") |
||||
.setting-content |
||||
.content-title |
||||
span {{_ 'settings'}} |
||||
.content-body |
||||
.side-menu |
||||
ul |
||||
li.active |
||||
a.js-setting-menu(data-id="registration-setting") {{_ 'registration'}} |
||||
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#registration-setting.setting-detail |
||||
li |
||||
a.flex.js-toggle-registration |
||||
.materialCheckBox(class="{{#if currentSetting.disableRegistration}}is-checked{{/if}}") |
||||
|
||||
span {{_ 'disable-self-registration'}} |
||||
li |
||||
.invite-people(class="{{#if currentSetting.disableRegistration}}{{else}}hide{{/if}}") |
||||
ul |
||||
li |
||||
.title {{_ 'invite-people'}} |
||||
textarea#email-to-invite.form-control(rows='5', placeholder="{{_ 'email-addresses'}}") |
||||
li |
||||
.title {{_ 'to-boards'}} |
||||
.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'}} |
||||
.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 {{_ 'smtp-port-description'}} |
||||
.form-group |
||||
input.form-control#mail-server-port(type="text", placeholder="25" value="{{currentSetting.mailServer.port}}") |
||||
li.smtp-form |
||||
.title {{_ 'smtp-username'}} |
||||
.form-group |
||||
input.form-control#mail-server-username(type="text", placeholder="{{_ 'username'}}" 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 {{_ 'send-from'}} |
||||
.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 |
@ -0,0 +1,128 @@ |
||||
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'], |
||||
}); |
||||
}, |
||||
toggleRegistration(){ |
||||
this.setLoading(true); |
||||
const registrationClosed = this.currentSetting().disableRegistration; |
||||
Settings.update(Settings.findOne()._id, {$set:{disableRegistration: !registrationClosed}}); |
||||
this.setLoading(false); |
||||
if(registrationClosed){ |
||||
$('.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('registration-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(){ |
||||
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()); |
||||
} |
||||
}); |
||||
if (validEmails.length) { |
||||
this.setLoading(true); |
||||
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-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, |
||||
'click button.js-save': this.saveMailServerInfo, |
||||
}]; |
||||
}, |
||||
}).register('setting'); |
@ -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); |
||||
|
@ -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 {{_ 'settings'}} |
||||
//TODO |
||||
// a.setting-header-btn.people |
||||
// i.fa(class="fa-users") |
||||
// span {{_ 'people'}} |
||||
|
||||
else |
||||
a.setting-header-btn.js-log-in( |
||||
title="{{_ 'log-in'}}") |
||||
i.fa.fa-sign-in |
||||
span {{_ 'log-in'}} |
@ -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 |
@ -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'], |
||||
}); |
||||
} |
@ -0,0 +1,116 @@ |
||||
Settings = new Mongo.Collection('settings'); |
||||
|
||||
Settings.attachSchema(new SimpleSchema({ |
||||
disableRegistration: { |
||||
type: Boolean, |
||||
}, |
||||
'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: 'Wekan', |
||||
}, |
||||
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 = {disableRegistration: 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); |
||||
}); |
||||
} |
||||
}); |
||||
}, |
||||
}); |
||||
} |
@ -0,0 +1,13 @@ |
||||
Meteor.publish('setting', () => { |
||||
return Settings.find({}, {fields:{disableRegistration: 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 []; |
||||
}); |
Loading…
Reference in new issue