From 2e1e75f8f6f38ec78023bb95d788be9499377fa2 Mon Sep 17 00:00:00 2001 From: Marcelo Schmidt Date: Tue, 11 Aug 2015 18:03:36 -0300 Subject: [PATCH 1/2] Creating users administration page under settings --- client/routes/router.coffee | 6 ++ client/stylesheets/base.less | 45 ++++++++++++ client/views/settings/settings.coffee | 2 - client/views/settings/settingsFlex.coffee | 2 +- client/views/settings/settingsFlex.html | 3 + client/views/settings/settingsUsers.coffee | 81 ++++++++++++++++++++++ client/views/settings/settingsUsers.html | 75 ++++++++++++++++++++ i18n/en.i18n.json | 3 + server/publications/fullUsers.coffee | 36 ++++++++++ 9 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 client/views/settings/settingsUsers.coffee create mode 100644 client/views/settings/settingsUsers.html create mode 100644 server/publications/fullUsers.coffee diff --git a/client/routes/router.coffee b/client/routes/router.coffee index 787d3c18feb6..fed11b619f29 100644 --- a/client/routes/router.coffee +++ b/client/routes/router.coffee @@ -39,6 +39,12 @@ FlowRouter.route '/changeavatar', action: -> BlazeLayout.render 'main', {center: 'avatarPrompt'} +FlowRouter.route '/settings/users', + name: 'settings-users' + + action: -> + BlazeLayout.render 'main', {center: 'settingsUsers'} + FlowRouter.route '/settings/:group?', name: 'settings' diff --git a/client/stylesheets/base.less b/client/stylesheets/base.less index 02daabb682ac..7e6cdf37dfaf 100644 --- a/client/stylesheets/base.less +++ b/client/stylesheets/base.less @@ -216,6 +216,30 @@ blockquote { top: 10px; font-weight: 400; } + .icon-spin4 { + position: absolute; + color: @secondary-font-color; + right: 5px; + top: 10px; + font-weight: 400; + -webkit-animation-name: spin; + -webkit-animation-duration: 2000ms; + -webkit-animation-iteration-count: infinite; + -webkit-animation-timing-function: linear; + -moz-animation-name: spin; + -moz-animation-duration: 2000ms; + -moz-animation-iteration-count: infinite; + -moz-animation-timing-function: linear; + -ms-animation-name: spin; + -ms-animation-duration: 2000ms; + -ms-animation-iteration-count: infinite; + -ms-animation-timing-function: linear; + + animation-name: spin; + animation-duration: 2000ms; + animation-iteration-count: infinite; + animation-timing-function: linear; + } input { padding-left: 25px; } @@ -256,6 +280,27 @@ blockquote { } } +@-ms-keyframes spin { + from { -ms-transform: rotate(0deg); } + to { -ms-transform: rotate(360deg); } +} +@-moz-keyframes spin { + from { -moz-transform: rotate(0deg); } + to { -moz-transform: rotate(360deg); } +} +@-webkit-keyframes spin { + from { -webkit-transform: rotate(0deg); } + to { -webkit-transform: rotate(360deg); } +} +@keyframes spin { + from { + transform:rotate(0deg); + } + to { + transform:rotate(360deg); + } +} + .rocket-h2 { font-weight: 300; text-transform: uppercase; diff --git a/client/views/settings/settings.coffee b/client/views/settings/settings.coffee index 2f5f9067cc45..5c94f76c2d64 100644 --- a/client/views/settings/settings.coffee +++ b/client/views/settings/settings.coffee @@ -1,8 +1,6 @@ Template.settings.helpers isAdmin: -> return Meteor.user().admin is true - groups: -> - return Settings.find({type: 'group'}).fetch() group: -> group = FlowRouter.getParam('group') group ?= Settings.findOne({ type: 'group' })?._id diff --git a/client/views/settings/settingsFlex.coffee b/client/views/settings/settingsFlex.coffee index 28c7d48b344e..8cce52c713b1 100644 --- a/client/views/settings/settingsFlex.coffee +++ b/client/views/settings/settingsFlex.coffee @@ -1,6 +1,6 @@ Template.settingsFlex.helpers groups: -> - return Settings.find({type: 'group'}).fetch() + return Settings.find({type: 'group'}, { sort: { sort: 1, i18nLabel: 1 } }).fetch() label: -> return TAPi18next.t @i18nLabel diff --git a/client/views/settings/settingsFlex.html b/client/views/settings/settingsFlex.html index 2a4f9177f6ae..ed47148c5cc2 100644 --- a/client/views/settings/settingsFlex.html +++ b/client/views/settings/settingsFlex.html @@ -12,6 +12,9 @@

{{_ "Settings"}}

{{_ i18nLabel}} {{/each}} +
  • + {{_ "Users"}} +
  • diff --git a/client/views/settings/settingsUsers.coffee b/client/views/settings/settingsUsers.coffee new file mode 100644 index 000000000000..068855c5d895 --- /dev/null +++ b/client/views/settings/settingsUsers.coffee @@ -0,0 +1,81 @@ +Template.settingsUsers.helpers + isAdmin: -> + return Meteor.user().admin is true + isReady: -> + return Template.instance().ready?.get() + users: -> + filter = _.trim Template.instance().filter?.get() + if filter + filterReg = new RegExp filter, "i" + query = { $or: [ { username: filterReg }, { name: filterReg }, { "emails.address": filterReg } ] } + else + query = {} + + return Meteor.users.find(query).fetch() + name: -> + return if @name then @name else TAPi18next.t 'project:Unnamed' + email: -> + return @emails?[0]?.address + flexOpened: -> + return 'opened' if Session.equals('flexOpened', true) + arrowPosition: -> + return 'left' unless Session.equals('flexOpened', true) + userData: -> + return Meteor.users.findOne Session.get 'settingsUsersSelected' + phoneNumber: -> + return '' unless @phoneNumber + if @phoneNumber.length > 10 + return "(#{@phoneNumber.substr(0,2)}) #{@phoneNumber.substr(2,5)}-#{@phoneNumber.substr(7)}" + else + return "(#{@phoneNumber.substr(0,2)}) #{@phoneNumber.substr(2,4)}-#{@phoneNumber.substr(6)}" + lastLogin: -> + if @lastLogin + return moment(@lastLogin).format('LLL') + utcOffset: -> + if @utcOffset? + if @utcOffset > 0 + @utcOffset = "+#{@utcOffset}" + + return "UTC #{@utcOffset}" + +Template.settingsUsers.onCreated -> + instance = @ + @loaded = new ReactiveVar 0 + @limit = new ReactiveVar 50 + @skip = new ReactiveVar 0 + @filter = new ReactiveVar '' + @ready = new ReactiveVar true + + @autorun -> + filter = instance.filter.get() + limit = instance.limit.get() + skip = instance.skip.get() + subscription = instance.subscribe 'fullUsers', filter, limit, skip + instance.ready.set subscription.ready() + +Template.settingsUsers.onRendered -> + Tracker.afterFlush -> + SideNav.setFlex "settingsFlex" + SideNav.openFlex() + +Template.settingsUsers.events + 'keydown #users-filter': (e) -> + if e.which is 13 + e.stopPropagation() + e.preventDefault() + + 'keyup #users-filter': (e, t) -> + e.stopPropagation() + e.preventDefault() + t.filter.set e.currentTarget.value + + 'click .flex-tab .more': -> + if (Session.get('flexOpened')) + Session.set('flexOpened',false) + else + Session.set('flexOpened', true) + + 'click .user-info': (e) -> + e.preventDefault() + Session.set 'settingsUsersSelected', $(e.currentTarget).data('id') + Session.set 'flexOpened', true diff --git a/client/views/settings/settingsUsers.html b/client/views/settings/settingsUsers.html new file mode 100644 index 000000000000..daaba1d347a0 --- /dev/null +++ b/client/views/settings/settingsUsers.html @@ -0,0 +1,75 @@ + diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 849df14d52bb..f6a6587981b7 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -84,9 +84,11 @@ "Language" : "Language", "Language_Version" : "English Version", "Last_message" : "Last message", + "Last_seen" : "Last seen", "Leave_room" : "Leave room", "line" : "line", "Load_more" : "Load more", + "Loading..." : "Loading...", "Loading_suggestion" : "Loading suggestions...", "Login" : "Login", "Login_with" : "Login with %s", @@ -181,6 +183,7 @@ "Submit" : "Submit", "The_field_is_required" : "The field %s is required.", "True" : "True", + "Unnamed" : "Unnamed", "Use_initials_avatar" : "Use your username initials", "use_menu" : "Use the side menu to access your rooms and chats", "Use_service_avatar" : "Use %s avatar", diff --git a/server/publications/fullUsers.coffee b/server/publications/fullUsers.coffee new file mode 100644 index 000000000000..5ccbe2470f3d --- /dev/null +++ b/server/publications/fullUsers.coffee @@ -0,0 +1,36 @@ +Meteor.publish 'fullUsers', (filter, limit, skip) -> + unless this.userId + return this.ready() + + user = Meteor.users.findOne this.userId + if user.admin isnt true + return this.ready() + + filter = _.trim filter + if filter + filterReg = new RegExp filter, "i" + query = { $or: [ { username: filterReg }, { name: filterReg }, { "emails.address": filterReg } ] } + else + query = {} + + limit = Math.min limit, 50 + + console.log '[publish] fullUsers'.green, filter, limit, skip + + Meteor.users.find query, + fields: + name: 1 + username: 1 + emails: 1 + phone: 1 + status: 1 + statusDefault: 1 + statusConnection: 1 + avatarOrigin: 1 + admin: 1 + utcOffset: 1 + language: 1 + lastLogin: 1 + utcOffset: 1 + limit: limit + skip: skip From 7bd299f6da870bcd23b753dbf331575f4fbba595 Mon Sep 17 00:00:00 2001 From: Marcelo Schmidt Date: Wed, 12 Aug 2015 17:55:36 -0300 Subject: [PATCH 2/2] Settings/Users --- client/methods/setUserActiveStatus.coffee | 4 ++ client/views/settings/settingsUsers.coffee | 45 +++++++++++++++++++--- client/views/settings/settingsUsers.html | 7 +++- server/lib/accounts.coffee | 4 ++ server/methods/deleteUser.coffee | 11 ++++++ server/methods/setUserActiveStatus.coffee | 15 ++++++++ server/publications/fullUsers.coffee | 8 ++-- server/startup/migrations/v13.coffee | 7 ++++ 8 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 client/methods/setUserActiveStatus.coffee create mode 100644 server/methods/deleteUser.coffee create mode 100644 server/methods/setUserActiveStatus.coffee create mode 100644 server/startup/migrations/v13.coffee diff --git a/client/methods/setUserActiveStatus.coffee b/client/methods/setUserActiveStatus.coffee new file mode 100644 index 000000000000..ac4c9614e43a --- /dev/null +++ b/client/methods/setUserActiveStatus.coffee @@ -0,0 +1,4 @@ +Meteor.methods + setUserActiveStatus: (userId, active) -> + Meteor.users.update userId, { $set: { active: active } } + return true \ No newline at end of file diff --git a/client/views/settings/settingsUsers.coffee b/client/views/settings/settingsUsers.coffee index 068855c5d895..fe2e3b3c869f 100644 --- a/client/views/settings/settingsUsers.coffee +++ b/client/views/settings/settingsUsers.coffee @@ -10,8 +10,7 @@ Template.settingsUsers.helpers query = { $or: [ { username: filterReg }, { name: filterReg }, { "emails.address": filterReg } ] } else query = {} - - return Meteor.users.find(query).fetch() + return Meteor.users.find(query, { limit: Template.instance().limit?.get(), sort: { username: 1 } }).fetch() name: -> return if @name then @name else TAPi18next.t 'project:Unnamed' email: -> @@ -40,17 +39,14 @@ Template.settingsUsers.helpers Template.settingsUsers.onCreated -> instance = @ - @loaded = new ReactiveVar 0 @limit = new ReactiveVar 50 - @skip = new ReactiveVar 0 @filter = new ReactiveVar '' @ready = new ReactiveVar true @autorun -> filter = instance.filter.get() limit = instance.limit.get() - skip = instance.skip.get() - subscription = instance.subscribe 'fullUsers', filter, limit, skip + subscription = instance.subscribe 'fullUsers', filter, limit instance.ready.set subscription.ready() Template.settingsUsers.onRendered -> @@ -79,3 +75,40 @@ Template.settingsUsers.events e.preventDefault() Session.set 'settingsUsersSelected', $(e.currentTarget).data('id') Session.set 'flexOpened', true + + 'click .deactivate': -> + Meteor.call 'setUserActiveStatus', Session.get('settingsUsersSelected'), false, (error, result) -> + if result + toastr.success t('User_has_been_deactivated') + if error + toastr.error error.reason + + 'click .activate': -> + Meteor.call 'setUserActiveStatus', Session.get('settingsUsersSelected'), true, (error, result) -> + if result + toastr.success t('User_has_been_activated') + if error + toastr.error error.reason + + 'click .delete': -> + swal { + title: t('Are_you_sure') + text: t('Delete_User_Warning') + type: 'warning' + showCancelButton: true + confirmButtonColor: '#DD6B55' + confirmButtonText: t('Yes_delete_it') + cancelButtonText: t('Cancel') + closeOnConfirm: false + html: false + }, -> + swal + title: t('Deleted') + text: t('User_has_been_deleted') + type: 'success' + timer: 2000 + showConfirmButton: false + + Meteor.call 'deleteUser', Session.get('settingsUsersSelected'), (error, result) -> + if error + toastr.error error.reason \ No newline at end of file diff --git a/client/views/settings/settingsUsers.html b/client/views/settings/settingsUsers.html index daaba1d347a0..3d03106b4118 100644 --- a/client/views/settings/settingsUsers.html +++ b/client/views/settings/settingsUsers.html @@ -56,7 +56,8 @@

    {{name}}

    {{> avatar username=username}}
    -

    {{username}}

    +

    {{name}}

    +

    {{username}}

    {{#if utcOffset}}

    {{utcOffset}}

    {{/if}} {{#each emails}}

    {{address}}{{#if verified}} {{/if}}

    {{/each}} {{#each phone}}

    {{phoneNumber}}

    {{/each}} @@ -64,7 +65,11 @@

    {{username}}

    {{/with}} diff --git a/server/lib/accounts.coffee b/server/lib/accounts.coffee index a9b5a2c68f75..6d1dfc91310c 100644 --- a/server/lib/accounts.coffee +++ b/server/lib/accounts.coffee @@ -53,6 +53,10 @@ Accounts.validateLoginAttempt (login) -> if login.allowed isnt true return login.allowed + if login.user?.active isnt true + throw new Meteor.Error 'inactive-user', 'Your_user_has_been_deactivated' + return false + if login.type is 'password' and RocketChat.settings.get 'Accounts_denyUnverifiedEmails' is true validEmail = login.user.emails.filter (email) -> return email.verified is true diff --git a/server/methods/deleteUser.coffee b/server/methods/deleteUser.coffee new file mode 100644 index 000000000000..fd935f0f9a4b --- /dev/null +++ b/server/methods/deleteUser.coffee @@ -0,0 +1,11 @@ +Meteor.methods + deleteUser: (userId) -> + if not Meteor.userId() + throw new Meteor.Error('invalid-user', "[methods] deleteUser -> Invalid user") + + user = Meteor.users.findOne Meteor.userId() + unless user?.admin is true + throw new Meteor.Error 'not-authorized', '[methods] deleteUser -> Not authorized' + + return true + # Meteor.users.remove userId \ No newline at end of file diff --git a/server/methods/setUserActiveStatus.coffee b/server/methods/setUserActiveStatus.coffee new file mode 100644 index 000000000000..e56878646f30 --- /dev/null +++ b/server/methods/setUserActiveStatus.coffee @@ -0,0 +1,15 @@ +Meteor.methods + setUserActiveStatus: (userId, active) -> + if not Meteor.userId() + throw new Meteor.Error 'invalid-user', '[methods] setUserActiveStatus -> Invalid user' + + user = Meteor.users.findOne Meteor.userId() + unless user?.admin is true + throw new Meteor.Error 'not-authorized', '[methods] setUserActiveStatus -> Not authorized' + + Meteor.users.update userId, { $set: { active: active } } + + if active is false + Meteor.users.update userId, { $set: { "services.resume.loginTokens" : [] } } + + return true \ No newline at end of file diff --git a/server/publications/fullUsers.coffee b/server/publications/fullUsers.coffee index 5ccbe2470f3d..6073f2452b5d 100644 --- a/server/publications/fullUsers.coffee +++ b/server/publications/fullUsers.coffee @@ -1,4 +1,4 @@ -Meteor.publish 'fullUsers', (filter, limit, skip) -> +Meteor.publish 'fullUsers', (filter, limit) -> unless this.userId return this.ready() @@ -15,7 +15,7 @@ Meteor.publish 'fullUsers', (filter, limit, skip) -> limit = Math.min limit, 50 - console.log '[publish] fullUsers'.green, filter, limit, skip + console.log '[publish] fullUsers'.green, filter, limit Meteor.users.find query, fields: @@ -31,6 +31,6 @@ Meteor.publish 'fullUsers', (filter, limit, skip) -> utcOffset: 1 language: 1 lastLogin: 1 - utcOffset: 1 + active: 1 limit: limit - skip: skip + sort: { username: 1 } \ No newline at end of file diff --git a/server/startup/migrations/v13.coffee b/server/startup/migrations/v13.coffee new file mode 100644 index 000000000000..be8b90dbee5d --- /dev/null +++ b/server/startup/migrations/v13.coffee @@ -0,0 +1,7 @@ +Meteor.startup -> + Migrations.add + version: 13 + up: -> + # Set all current users as active + Meteor.users.update {}, { $set: { active: true } }, { multi: true } + console.log "Set all users as active" \ No newline at end of file