diff --git a/README.md b/README.md index c34e45d..0586a38 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ A roles based account management system using bootstrap 3 for Meteor. - [History](#history) - [Quick Start](#quick-start) - [Iron Router Integration](#iron-router-integration) +- [Configuration & Optional Features](#configuration) - [Contributing](#contributing) ## TODO @@ -111,7 +112,7 @@ if (Meteor.isClient) { + + + + {{#each fields}} + + {{/each}} + + + + {{#each users}} + + {{#each fields}} + + {{/each}} + + {{/each}} + +
{{> accountsAdminHeader}}
{{> accountsAdminField field=. user=..}}
+ Previous {{pageSize}} users + Next {{pageSize}} users + + {{> updateRolesModal}} + {{> deleteAccountModal}} + {{> infoAccountModal}} + {{> updateAccountModal}} + {{> impersonateAccountModal}} + + + + + + + + diff --git a/client/accounts_admin.js b/client/accounts_admin.js index 785f781..9f6d864 100644 --- a/client/accounts_admin.js +++ b/client/accounts_admin.js @@ -1,71 +1,211 @@ +/* global Roles, AccountsAdmin */ +"use strict"; + +var getRoles = function(value) { + if (_.isArray(value)) { + return value.join(', '); + } else if (_.isObject(value)) { + var roles = []; + if (_.isArray(value[Roles.GLOBAL_GROUP])) { + value[Roles.GLOBAL_GROUP].forEach(function(role) { + roles.push(role); + }); + } + _.keys(value).forEach(function(group) { + if (group !== Roles.GLOBAL_GROUP) { + value[group].forEach(function(role) { + roles.push(role); + }); + } + }); + return roles.join(', '); + } +}; + +var getEmail = function(value, user) { + + if (user.emails && user.emails.length) + return user.emails[0].address; + + if (user.services) { + //Iterate through services + _.keys(user.services).forEach(function(serviceName) { + var serviceObject = user.services[serviceName]; + //If an 'id' isset then assume valid service + if (serviceObject.id) { + if (serviceObject.email) { + return serviceObject.email; + } + } + }); + } + return ""; +}; + Template.accountsAdmin.helpers({ - users: function() { - return filteredUserQuery(Meteor.userId(), Session.get("userFilter")); - }, - - email: function () { - if (this.emails && this.emails.length) - return this.emails[0].address; - - if (this.services) { - //Iterate through services - for (var serviceName in this.services) { - var serviceObject = this.services[serviceName]; - //If an 'id' isset then assume valid service - if (serviceObject.id) { - if (serviceObject.email) { - return serviceObject.email; - } - } - } - } - return ""; - }, - - searchFilter: function() { - return Session.get("userFilter"); - }, - - myself: function(userId) { - return Meteor.userId() === userId; - } + myself: function(userId) { + return Meteor.userId() === userId; + }, + fields: function() { + var fields = [{ + key: '', + label: '', + tmpl: Template.accountsAdminControlPanel + }, { + key: 'emails.0.address', + label: 'Email', + fn: function(value, user) { + return getEmail(value, user); + } + }, { + key: 'profile.name', + label: 'Name' + }, { + key: 'profile.businessName', + label: 'Account Name' + }, { + key: 'roles', + label: 'Roles', + fn: function(value) { + return getRoles(value); + } + }, { + key: 'profile.businessNeeds', + label: 'Business Description', + }, { + key: 'profile.useCore', + label: 'Core', + }, { + key: 'profile.usePro', + label: 'Pro', + }, { + key: 'createdAt', + label: 'Created?', + fn: function(value) { + return value && value.toDateString(); + } + }, ]; + if (AccountsAdmin.config.userStatus) { + fields.push({ + key: 'status.lastLogin.date', + label: 'Last Login', + fn: function(value) { + return value && value.toLocaleString(); + } + }); + fields.push({ + key: 'status.online', + label: 'Online?' + }); + } + return fields; + }, + users: function() { + var sortKey = Session.get("accountsAdminSortKey"); + var sort = {}; + sort[sortKey.sort] = sortKey.direction; + return AccountsAdmin.filteredUserQuery(Meteor.userId(), { + filter: Session.get("accountsAdminUserFilter"), + sort: sort + }); + }, + searchFilter: function() { + return Session.get("accountsAdminUserFilter"); + }, + maxUsersPerPage: function() { + return AccountsAdmin.config.maxUsersPerPage; + } }); // search no more than 2 times per second var setUserFilter = _.throttle(function(template) { - var search = template.find(".search-input-filter").value; - Session.set("userFilter", search); + var search = template.find(".search-input-filter").value; + Session.set("accountsAdminUserFilter", search); }, 500); Template.accountsAdmin.events({ - 'keyup .search-input-filter': function(event, template) { - setUserFilter(template); - return false; - }, + 'keyup .search-input-filter': function(event, template) { + setUserFilter(template); + return false; + }, + 'click .clickable': function() { + Session.set('userInScope', this.user); + }, + 'click .showMore': function(event) { + event.preventDefault(); + var skip = Session.get("accountsAdminSkip") || 0; + skip += (AccountsAdmin.config.maxUsersPerPage * +event.target.dataset.direction); + Session.set("accountsAdminSkip", skip); + }, +}); - 'click .glyphicon-trash': function(event, template) { - Session.set('userInScope', this); - }, +Template.accountsAdmin.created = function() { + Session.set("accountsAdminSortKey", { + key: 'username', + direction: 1 + }); +}; - 'click .glyphicon-info-sign': function(event, template) { - Session.set('userInScope', this); - }, - 'click .glyphicon-pencil': function(event, template) { - Session.set('userInScope', this); - } +Template.accountsAdmin.rendered = function() { + var searchElement = document.getElementsByClassName('search-input-filter'); + if (!searchElement) + return; + var filterValue = Session.get("accountsAdminUserFilter"); + + var pos = 0; + if (filterValue) + pos = filterValue.length; + + searchElement[0].focus(); + searchElement[0].setSelectionRange(pos, pos); +}; + +Template.accountsAdmin.destroyed = function() { + //clean up the session + Session.set('userInScope', undefined); + Session.set('accountsAdminSortKey', undefined); +}; + +Template.accountsAdminHeader.helpers({ + header: function() { + return !_.isUndefined(this.label) && this.label || this.key; + }, + sortDown: function() { + var sort = Session.get("accountsAdminSortKey"); + return (sort.key === this.key && sort.direction === 1); + }, + sortUp: function() { + var sort = Session.get("accountsAdminSortKey"); + return (sort.key === this.key && sort.direction === -1); + }, }); -Template.accountsAdmin.rendered = function() { - var searchElement = document.getElementsByClassName('search-input-filter'); - if(!searchElement) - return; - var filterValue = Session.get("userFilter"); - - var pos = 0; - if (filterValue) - pos = filterValue.length; - - searchElement[0].focus(); - searchElement[0].setSelectionRange(pos, pos); -}; \ No newline at end of file +Template.accountsAdminHeader.events({ + 'click .sortIndicator': function(event) { + event.preventDefault(); + var sort = Session.get("accountsAdminSortKey"); + sort.direction *= -1; + sort.key = this.key; + Session.set("accountsAdminSortKey", sort); + } +}); + +function index(obj, i) { + return obj && obj[i]; +} +Template.accountsAdminField.helpers({ + val: function() { + var val = this && this.field.key.split('.').reduce(index, this.user); + return this.field.fn && this.field.fn(val, this.user) || val; + }, +}); + +Template.accountsAdminControlPanel.helpers({ + myself: function(userId) { + return Meteor.userId() === userId; + }, + allowImpersonate: function() { + return AccountsAdmin.config.allowImpersonation; + }, +}); diff --git a/client/delete_account_modal.html b/client/delete_account_modal.html index cd99739..152d329 100644 --- a/client/delete_account_modal.html +++ b/client/delete_account_modal.html @@ -18,4 +18,4 @@

Are you sure you want to delete {{email}}?

{{/with}} - \ No newline at end of file + diff --git a/client/delete_account_modal.js b/client/delete_account_modal.js index bab4898..35fd106 100644 --- a/client/delete_account_modal.js +++ b/client/delete_account_modal.js @@ -1,39 +1,44 @@ +/* global Errors */ + +"use strict"; Template.deleteAccountModalInner.helpers({ - email: function () { - if (this.emails && this.emails.length) - return this.emails[0].address; + email: function() { + if (this.emails && this.emails.length) + return this.emails[0].address; - if (this.services) { - //Iterate through services - for (var serviceName in this.services) { - var serviceObject = this.services[serviceName]; - //If an 'id' isset then assume valid service - if (serviceObject.id) { - if (serviceObject.email) { - return serviceObject.email; - } - } - } - } - return ""; - }, - userInScope: function() { - return Session.get('userInScope'); - } + if (this.services) { + //Iterate through services + for (var serviceName in this.services) { + if (this.services.hasOwnProperty(serviceName)) { + var serviceObject = this.services[serviceName]; + //If an 'id' isset then assume valid service + if (serviceObject.id) { + if (serviceObject.email) { + return serviceObject.email; + } + } + } + } + } + return ""; + }, + userInScope: function() { + return Session.get('userInScope'); + } }); Template.deleteAccountModalInner.events({ - 'click .btn-danger': function(event, template) { - Meteor.call('deleteUser', this._id, function(error) { - if (error) { - // optionally use a meteor errors package - if (typeof Errors === "undefined") - Log.error('Error: ' + error.reason); - else { - Errors.throw(error.reason); - } - } - $("#deleteaccount").modal("hide"); - }); - } -}); \ No newline at end of file + 'click .btn-danger': function() { + Meteor.call('deleteUser', this._id, function(error) { + if (error) { + // optionally use a meteor errors package + if (typeof Errors === "undefined") + Log.error('Error: ' + error.reason); + else { + Errors.throw(error.reason); + } + } + $("#deleteaccount").modal("hide"); + }); + } +}); diff --git a/client/impersonate_account_modal.html b/client/impersonate_account_modal.html new file mode 100644 index 0000000..a6e3dd0 --- /dev/null +++ b/client/impersonate_account_modal.html @@ -0,0 +1,24 @@ + + + diff --git a/client/impersonate_account_modal.js b/client/impersonate_account_modal.js new file mode 100644 index 0000000..7a5b005 --- /dev/null +++ b/client/impersonate_account_modal.js @@ -0,0 +1,28 @@ +/* global AccountsAdmin */ +"use strict"; + +Template.impersonateAccountModalInner.helpers({ + userInScope: function() { + return Session.get('userInScope'); + }, +}); + + +Template.impersonateAccountModalInner.events({ + 'click .btn-danger': function() { + var self = this; + Meteor.call('impersonateUser', self._id, function(error) { + if (error) { + console.error("Render impersonate got error: ", error); + } else { + Meteor.connection.setUserId(self._id); + $('body').removeClass('modal-open'); + $('.modal-backdrop').remove(); + + if (AccountsAdmin.config.impersonationSuccess) { + AccountsAdmin.config.impersonationSuccess(); + } + } + }); + } +}); diff --git a/client/info_account_modal.html b/client/info_account_modal.html index f015768..a5b32c5 100644 --- a/client/info_account_modal.html +++ b/client/info_account_modal.html @@ -26,8 +26,8 @@

Account Info

  • Email{{email}}
  • ID{{_id}}
  • - {{#each rolePairs}} -
  • {{key}}{{value}}
  • + {{#each roles}} +
  • {{this}}
  • {{/each}} @@ -35,4 +35,4 @@

    Account Info

    {{/with}} - \ No newline at end of file + diff --git a/client/info_account_modal.js b/client/info_account_modal.js index 0a1577b..dc5e74e 100644 --- a/client/info_account_modal.js +++ b/client/info_account_modal.js @@ -1,40 +1,51 @@ -Template.infoAccountModalInner.helpers({ - email: function () { - if (this.emails && this.emails.length) - return this.emails[0].address; +/* global Roles */ +"use strict"; - if (this.services) { - //Iterate through services - for (var serviceName in this.services) { - var serviceObject = this.services[serviceName]; - //If an 'id' isset then assume valid service - if (serviceObject.id) { - if (serviceObject.email) { - return serviceObject.email; - } - } - } - } - return ""; - }, +Template.infoAccountModalInner.helpers({ + email: function() { + if (this.emails && this.emails.length) + return this.emails[0].address; - userInScope: function() { - return Session.get('userInScope'); - }, + if (this.services) { + //Iterate through services + for (var serviceName in this.services) { + if (this.services.hasOwnProperty(serviceName)) { + var serviceObject = this.services[serviceName]; + //If an 'id' isset then assume valid service + if (serviceObject.id) { + if (serviceObject.email) { + return serviceObject.email; + } + } + } + } + } + return ""; + }, - rolePairs: function() { - var pairs = []; - if (!this.roles) - pairs.push({key: 'Roles', value: 'None'}); + userInScope: function() { + return Session.get('userInScope'); + }, - for (var role in this.roles) { - var r = this.roles[role]; - if (role === '0') { - pairs.push({key: 'Roles', value: r}); - } else { - pairs.push({key: '-', value: r}); - } - } - return pairs; - } + roles: function() { + var self = this; + var roles = []; + if (_.isArray(self.roles)) { + roles = self.roles; + } else if (_.isObject(self.roles)) { + if (_.isArray(self.roles[Roles.GLOBAL_GROUP])) { + self.roles[Roles.GLOBAL_GROUP].forEach(function(role) { + roles.push(role); + }); + } + _.keys(self.roles).forEach(function(group) { + if (group !== Roles.GLOBAL_GROUP) { + self.roles[group].forEach(function(role) { + roles.push(role + ' (' + group + ')'); + }); + } + }); + } + return roles; + } }); diff --git a/client/startup.js b/client/startup.js index 71e7285..be28c32 100644 --- a/client/startup.js +++ b/client/startup.js @@ -1,6 +1,20 @@ +/* global AccountsAdmin */ +"use strict"; + +AccountsAdmin.subscribe = function() { + return [Meteor.subscribe('roles'), + Meteor.subscribe('filteredUsers', { + filter: Session.get('accountsAdminUserFilter') || '', + skip: Session.get("accountsAdminSkip") || null, + sort: Session.get("accountsAdminSortKey") || null, + })]; +}; + + Meteor.startup(function() { - Meteor.subscribe('roles'); - Deps.autorun(function(e) { - Meteor.subscribe('filteredUsers', Session.get('userFilter')); - }); -}); \ No newline at end of file + if (!AccountsAdmin.config.manualSubscriptions) { + Tracker.autorun(function() { + AccountsAdmin.subscribe(); + }); + } +}); diff --git a/client/update_account_modal.html b/client/update_account_modal.html index b8436db..95b09b2 100644 --- a/client/update_account_modal.html +++ b/client/update_account_modal.html @@ -17,9 +17,13 @@

    Update {{email}}

    Name - +
    -
    +
    + Password + +
    + {{#if roles}}