Files
cloudron-box/webadmin/src/views/users.js

588 lines
21 KiB
JavaScript
Raw Normal View History

2018-01-22 13:01:38 -08:00
'use strict';
/* global Clipboard */
// poor man's async
function asyncForEach(items, handler, callback) {
var cur = 0;
if (items.length === 0) return callback();
(function iterator() {
handler(items[cur], function () {
if (cur >= items.length-1) return callback();
++cur;
iterator();
});
})();
}
2018-01-22 13:01:38 -08:00
angular.module('Application').controller('UsersController', ['$scope', '$location', '$timeout', 'Client', function ($scope, $location, $timeout, Client) {
Client.onReady(function () { if (!Client.getUserInfo().admin) $location.path('/'); });
$scope.ready = false;
$scope.users = [];
$scope.groups = [];
$scope.groupsById = { };
$scope.config = Client.getConfig();
$scope.userInfo = Client.getUserInfo();
$scope.emailDomains = [];
// FIXME this needs a whole lot of rework as it is used for alias ui, which now needs to be multidomain aware
$scope.mailConfig = {
enabled: false
};
2018-01-22 13:01:38 -08:00
$scope.userremove = {
busy: false,
error: {},
userInfo: {},
password: '',
show: function (userInfo) {
$scope.userremove.error.username = null;
$scope.userremove.error.password = null;
$scope.userremove.password = '';
$scope.userremove.userInfo = userInfo;
$scope.userremove_form.$setPristine();
$scope.userremove_form.$setUntouched();
$('#userRemoveModal').modal('show');
},
submit: function () {
$scope.userremove.error.password = null;
$scope.userremove.busy = true;
Client.removeUser($scope.userremove.userInfo.id, $scope.userremove.password, function (error) {
$scope.userremove.busy = false;
if (error && error.statusCode === 403) {
$scope.userremove.error.password = 'Wrong password';
$scope.userremove.password = '';
$scope.userremove_form.password.$setPristine();
$('#inputUserRemovePassword').focus();
return;
}
if (error) return console.error('Unable to delete user.', error);
$scope.userremove.userInfo = {};
$scope.userremove.password = '';
$scope.userremove_form.$setPristine();
$scope.userremove_form.$setUntouched();
refresh();
$('#userRemoveModal').modal('hide');
});
}
};
$scope.useradd = {
busy: false,
alreadyTaken: false,
error: {},
email: '',
username: '',
displayName: '',
sendInvite: true,
show: function () {
$scope.useradd.error = {};
$scope.useradd.email = '';
$scope.useradd.username = '';
$scope.useradd.displayName = '';
$scope.useradd_form.$setUntouched();
$scope.useradd_form.$setPristine();
$('#userAddModal').modal('show');
},
submit: function () {
$scope.useradd.busy = true;
$scope.useradd.alreadyTaken = false;
$scope.useradd.error.email = null;
$scope.useradd.error.username = null;
$scope.useradd.error.displayName = null;
Client.createUser($scope.useradd.username || null, $scope.useradd.email, $scope.useradd.displayName, $scope.useradd.sendInvite, function (error) {
$scope.useradd.busy = false;
if (error && error.statusCode === 409) {
if (error.message.toLowerCase().indexOf('email') !== -1) {
$scope.useradd.error.email = 'Email already taken';
$scope.useradd_form.email.$setPristine();
$('#inputUserAddEmail').focus();
} else if (error.message.toLowerCase().indexOf('username') !== -1 || error.message.toLowerCase().indexOf('mailbox') !== -1) {
$scope.useradd.error.username = 'Username already taken';
$scope.useradd_form.username.$setPristine();
$('#inputUserAddUsername').focus();
} else {
// should not happen!!
console.error(error.message);
}
return;
}
if (error && error.statusCode === 400) {
if (error.message.toLowerCase().indexOf('email') !== -1) {
$scope.useradd.error.email = 'Invalid Email';
$scope.useradd.error.emailAttempted = $scope.useradd.email;
$scope.useradd_form.email.$setPristine();
$('#inputUserAddEmail').focus();
} else if (error.message.toLowerCase().indexOf('username') !== -1) {
$scope.useradd.error.username = error.message;
$scope.useradd_form.username.$setPristine();
$('#inputUserAddUsername').focus();
} else {
console.error('Unable to create user.', error.statusCode, error.message);
}
return;
}
if (error) return console.error('Unable to create user.', error.statusCode, error.message);
$scope.useradd.error = {};
$scope.useradd.email = '';
$scope.useradd.username = '';
$scope.useradd.displayName = '';
$scope.useradd_form.$setUntouched();
$scope.useradd_form.$setPristine();
refresh();
$('#userAddModal').modal('hide');
});
}
};
$scope.useredit = {
busy: false,
busyFetching: false,
2018-01-22 13:01:38 -08:00
error: {},
userInfo: {},
email: '',
fallbackEmail: '',
2018-01-25 18:17:40 +01:00
aliases: {},
emailAddresses: [],
availableEmailAddresses: [],
2018-01-22 13:01:38 -08:00
superuser: false,
show: function (userInfo) {
$scope.useredit.busyFetching = true;
2018-01-22 13:01:38 -08:00
$scope.useredit.error = {};
$scope.useredit.email = userInfo.email;
$scope.useredit.fallbackEmail = userInfo.fallbackEmail;
$scope.useredit.userInfo = userInfo;
$scope.useredit.groupIds = angular.copy(userInfo.groupIds);
$scope.useredit.superuser = userInfo.groupIds.indexOf('admin') !== -1;
$scope.useredit.availableEmailAddresses = $scope.emailDomains.map(function (d) { return userInfo.username + '@' + d.domain; });
$scope.useredit.emailAddresses = [];
2018-01-25 18:17:40 +01:00
$scope.useredit.aliases = {};
2018-01-22 13:01:38 -08:00
2018-01-25 18:17:40 +01:00
// fetch user's mailboxes and aliases
var tmp = [];
asyncForEach($scope.emailDomains, function (domain, callback) {
Client.getUserMailbox(domain.domain, userInfo.id, function (error) {
if (error) return callback();
2018-01-25 18:17:40 +01:00
var emailAddress = userInfo.username + '@' + domain.domain;
tmp.push(emailAddress);
Client.getAliases(domain.domain, userInfo.id, function (error, result) {
if (error) return callback();
2018-01-25 18:17:40 +01:00
$scope.useredit.aliases[emailAddress] = result.join(',');
callback();
});
});
}, function (error) {
$scope.useredit.busyFetching = false;
if (error) return console.error(error);
// we need this copy as angular multiselect cannot deal with dynamic arrays!
$scope.useredit.emailAddresses = tmp;
});
2018-01-22 13:01:38 -08:00
$scope.useredit_form.$setPristine();
$scope.useredit_form.$setUntouched();
// clear any alias error when the model changes. this is required because tagInput directive is not angular forms aware
// http://blog.revolunet.com/blog/2013/11/28/create-resusable-angularjs-input-component/ has some notes on how to do that
$scope.$watch('useredit.aliases', function () {
$scope.useredit.error.aliases = null;
});
$('#userEditModal').modal('show');
},
toggleGroup: function (group) {
var pos = $scope.useredit.groupIds.indexOf(group.id);
if (pos === -1) {
$scope.useredit.groupIds.push(group.id);
} else {
$scope.useredit.groupIds.splice(pos, 1);
}
},
submit: function () {
$scope.useredit.error = {};
$scope.useredit.busy = true;
var data = {
id: $scope.useredit.userInfo.id,
email: $scope.useredit.email,
fallbackEmail: $scope.useredit.fallbackEmail
2018-01-22 13:01:38 -08:00
};
Client.updateUser(data, function (error) {
if (error) {
$scope.useredit.busy = false;
if (error.statusCode === 409) {
$scope.useredit.error.email = 'Email already taken';
$scope.useredit_form.email.$setPristine();
$('#inputUserEditEmail').focus();
} else {
console.error('Unable to update user:', error);
}
return;
}
if ($scope.useredit.superuser) {
if ($scope.useredit.groupIds.indexOf('admin') === -1) $scope.useredit.groupIds.push('admin');
} else {
$scope.useredit.groupIds = $scope.useredit.groupIds.filter(function (groupId) { return groupId !== 'admin'; });
}
Client.setGroups(data.id, $scope.useredit.groupIds, function (error) {
if (error) return console.error('Unable to update groups for user:', error);
asyncForEach($scope.useredit.availableEmailAddresses, function (address, callback) {
var isEnabled = !!$scope.useredit.emailAddresses.find(function (a) { return a === address; });
2018-01-25 18:17:40 +01:00
var userId = $scope.useredit.userInfo.id;
var domain = $scope.emailDomains.find(function (d) { return address.split('@')[1] === d.domain; });
if (!domain) console.error('Domain not found. Internal error should not happen.');
2018-01-25 18:17:40 +01:00
var aliases = $scope.useredit.aliases[address] ? $scope.useredit.aliases[address].split(',') : [];
Client.setAliases(domain.domain, userId, aliases, function (error) {
if (error) return callback(error);
// TODO we could be smarter and check if the selection has actually changed
if (isEnabled) Client.enableUserMailbox(domain.domain, userId, callback);
else Client.disableUserMailbox(domain.domain, userId, callback);
});
}, function (error) {
2018-01-22 13:01:38 -08:00
$scope.useredit.busy = false;
if (error) return console.error('unable to adjust email addresses.', error);
2018-01-22 13:01:38 -08:00
$scope.useredit.userInfo = {};
$scope.useredit.email = '';
$scope.useredit.superuser = false;
$scope.useredit.groupIds = [];
$scope.useredit.emailAddresses = [];
2018-01-22 13:01:38 -08:00
$scope.useredit.aliases = '';
$scope.useredit_form.$setPristine();
$scope.useredit_form.$setUntouched();
refresh();
$('#userEditModal').modal('hide');
});
});
});
}
};
$scope.groupAdd = {
busy: false,
error: {},
name: '',
show: function () {
$scope.groupAdd.busy = false;
$scope.groupAdd.error = {};
$scope.groupAdd.name = '';
$scope.groupAddForm.$setUntouched();
$scope.groupAddForm.$setPristine();
$('#groupAddModal').modal('show');
},
submit: function () {
$scope.groupAdd.busy = true;
$scope.groupAdd.error = {};
Client.createGroup($scope.groupAdd.name, function (error) {
$scope.groupAdd.busy = false;
if (error && error.statusCode === 409) {
$scope.groupAdd.error.name = 'Name already taken';
$scope.groupAddForm.name.$setPristine();
$('#groupAddName').focus();
return;
}
if (error && error.statusCode === 400) {
$scope.groupAdd.error.name = error.message;
$scope.groupAddForm.name.$setPristine();
$('#groupAddName').focus();
return;
}
if (error) return console.error('Unable to create group.', error.statusCode, error.message);
refresh();
$('#groupAddModal').modal('hide');
});
}
};
$scope.inviteSent = {
email: '',
setupLink: ''
};
$scope.groupEdit = {
busy: false,
busyFetching: false,
error: {},
group: null,
lists: [],
availableLists: [],
show: function (group) {
$scope.groupEdit.busy = false;
$scope.groupEdit.busyFetching = true;
$scope.groupEdit.error = {};
$scope.groupEdit.availableLists = $scope.emailDomains.map(function (domain) { return { domain: domain, address: group.name + '@' + domain.domain }; });
$scope.groupEdit.selectedLists = [];
$scope.groupEdit.currentLists = [];
$scope.groupEdit.group = angular.copy(group);
var tmp = [];
asyncForEach($scope.emailDomains, function (domain, callback) {
Client.getMailingList(domain.domain, $scope.groupEdit.group.id, function (error) {
if (error) return callback(error);
var list = $scope.groupEdit.availableLists.find(function (list) { return list.domain.domain === domain.domain; });
tmp.push(list);
$scope.groupEdit.currentLists.push(list);
callback();
});
}, function (error) {
$scope.groupEdit.busyFetching = false;
if (error) return console.error('Unable to get mailing lists.', error);
$scope.groupEdit.selectedLists = tmp;
});
$scope.groupEditForm.$setUntouched();
$scope.groupEditForm.$setPristine();
$('#groupEditModal').modal('show');
},
submit: function () {
$scope.groupEdit.busy = true;
var addedLists = $scope.groupEdit.selectedLists.filter(function (s) {
return !$scope.groupEdit.currentLists.find(function (c) {
return c.domain.domain === s.domain.domain;
});
});
var removedLists = $scope.groupEdit.currentLists.filter(function (c) {
return !$scope.groupEdit.selectedLists.find(function (s) {
return s.domain.domain === c.domain.domain;
});
});
asyncForEach(addedLists, function (list, callback) {
Client.addMailingList(list.domain.domain, $scope.groupEdit.group.id, callback);
}, function (error) {
if (error) {
$scope.groupEdit.busy = false;
return console.error('Failed to add group to mailinglists.', error);
}
asyncForEach(removedLists, function (list, callback) {
Client.removeMailingList(list.domain.domain, $scope.groupEdit.group.id, callback);
}, function (error) {
$scope.groupEdit.busy = false;
if (error) {
return console.error('Failed to remove group to mailinglists.', error);
}
$('#groupEditModal').modal('hide');
});
});
}
};
2018-01-22 13:01:38 -08:00
$scope.groupRemove = {
busy: false,
error: {},
group: null,
password: '',
memberCount: 0,
show: function (group) {
$scope.groupRemove.busy = false;
$scope.groupRemove.error = {};
$scope.groupRemove.password = '';
$scope.groupRemove.group = angular.copy(group);
$scope.groupRemoveForm.$setUntouched();
$scope.groupRemoveForm.$setPristine();
Client.getGroup(group.id, function (error, result) {
if (error) return console.error('Unable to fetch group information.', error.statusCode, error.message);
$scope.groupRemove.memberCount = result.userIds.length;
$('#groupRemoveModal').modal('show');
});
},
submit: function () {
$scope.groupRemove.busy = true;
$scope.groupRemove.error = {};
Client.removeGroup($scope.groupRemove.group.id, $scope.groupRemove.password, function (error) {
$scope.groupRemove.busy = false;
if (error && error.statusCode === 403) {
$scope.groupRemove.error.password = 'Wrong password';
$scope.groupRemove.password = '';
$scope.groupRemoveForm.password.$setPristine();
$('#groupRemovePasswordInput').focus();
return;
}
if (error) return console.error('Unable to remove group.', error.statusCode, error.message);
refresh();
$('#groupRemoveModal').modal('hide');
});
}
};
$scope.isMe = function (user) {
return user.username === Client.getUserInfo().username;
};
$scope.isAdmin = function (user) {
return !!user.admin;
};
$scope.sendInvite = function (user) {
$scope.inviteSent.email = user.fallbackEmail;
$scope.inviteSent.setupLink = '';
Client.sendInvite(user, function (error, resetToken) {
if (error) return console.error(error);
// Client.notify('', 'Invitation was successfully sent to ' + user.email + '.', false, 'success');
$scope.inviteSent.setupLink = location.origin + '/api/v1/session/account/setup.html?reset_token=' + resetToken;
$('#inviteSentModal').modal('show');
});
};
$scope.copyToClipboard = function (value) {
document.execCommand('copy');
};
function refresh() {
Client.getGroups(function (error, result) {
if (error) return console.error('Unable to get group listing.', error);
$scope.groups = result;
$scope.groupsById = { };
for (var i = 0; i < result.length; i++) {
$scope.groupsById[result[i].id] = result[i];
}
Client.getUsers(function (error, result) {
if (error) return console.error('Unable to get user listing.', error);
$scope.users = result;
Client.getDomains(function (error, result) {
if (error) return console.error('Unable to get domain listing.', error);
// reset so we can push the fresh config
$scope.emailDomains = [];
asyncForEach(result, function (domain, callback) {
Client.getMailConfigForDomain(domain.domain, function (error, mailConfig) {
if (error) return callback(error);
domain.mailConfig = mailConfig;
// only collect domains where email is enabled
if (mailConfig.enabled) $scope.emailDomains.push(domain);
callback();
});
}, function (error) {
if (error) return console.error('Unable to get mail config for domains.', error);
$scope.ready = true;
});
});
2018-01-22 13:01:38 -08:00
});
});
}
Client.onReady(refresh);
2018-01-22 13:01:38 -08:00
// setup all the dialog focus handling
['userAddModal', 'userRemoveModal', 'userEditModal', 'groupAddModal', 'groupRemoveModal'].forEach(function (id) {
$('#' + id).on('shown.bs.modal', function () {
$(this).find("[autofocus]:first").focus();
});
});
var clipboard = new Clipboard('#setupLinkButton');
clipboard.on('success', function(e) {
$('#setupLinkButton').tooltip({
title: 'Copied!',
trigger: 'manual'
}).tooltip('show');
$timeout(function () { $('#setupLinkButton').tooltip('hide'); }, 2000);
e.clearSelection();
});
clipboard.on('error', function(e) {
$('#setupLinkButton').tooltip({
title: 'Press Ctrl+C to copy',
trigger: 'manual'
}).tooltip('show');
$timeout(function () { $('#setupLinkButton').tooltip('hide'); }, 2000);
});
$('.modal-backdrop').remove();
}]);