Initial commit
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
<ul class="DropdownList DropdownList--list CDB-Text CDB-Size-medium">
|
||||
<li class="DropdownList-item DropdownList-item--verticalPadding"><a href="<%- createUrl %>">Create User</a></li>
|
||||
<li class="DropdownList-item DropdownList-item--verticalPadding"><a href="#" class="js-inviteUser">Invite User</a></li>
|
||||
</ul>
|
||||
@@ -0,0 +1,35 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const Utils = require('builder/helpers/utils');
|
||||
const pluralize = require('dashboard/helpers/pluralize');
|
||||
const template = require('./organization-user.tpl');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'OrganizationList-user',
|
||||
|
||||
tagName: 'li',
|
||||
|
||||
render: function () {
|
||||
this.$el.html(
|
||||
template({
|
||||
totalPer: this.options.totalPer,
|
||||
userPer: this.options.userPer,
|
||||
usedPer: this.options.usedPer,
|
||||
isOwner: this.options.isOwner,
|
||||
isAdmin: this.options.isAdmin,
|
||||
isViewer: this.options.isViewer,
|
||||
editable: this.options.editable,
|
||||
url: this.options.url,
|
||||
sizeOnBytes: Utils.readablizeBytes(this.model.get('db_size_in_bytes')),
|
||||
quotaInBytes: Utils.readablizeBytes(this.model.get('quota_in_bytes')),
|
||||
avatarUrl: this.model.get('avatar_url'),
|
||||
username: this.model.get('username'),
|
||||
user_email: this.model.get('email'),
|
||||
table_count: pluralize.prefixWithCount('Dataset', 'Datasets', this.model.get('table_count')),
|
||||
maps_count: pluralize.prefixWithCount('Map', 'Maps', this.model.get('all_visualization_count'))
|
||||
})
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
<% if (editable) { %>
|
||||
<a class="OrganizationList-userLink" href="<%- url %>">
|
||||
<% } else { %>
|
||||
<span class="OrganizationList-userLink is-disabled">
|
||||
<% } %>
|
||||
<div class="OrganizationList-userAvatar UserAvatar">
|
||||
<img src="<%- avatarUrl %>" alt="<%- username %>" src="<%- username %>" class="UserAvatar-img UserAvatar-img--medium-large OrganizationList-userAvatar--img" />
|
||||
</div>
|
||||
<div class="OrganizationList-userInfo">
|
||||
<div class="OrganizationList-userInfoName">
|
||||
<div class="u-flex u-alignCenter">
|
||||
<h3 class="CDB-Text u-ellipsLongText CDB-Size-medium is-semibold u-rSpace" title="<%- username %>"><%- username %></h3>
|
||||
<% if (isOwner) { %>
|
||||
<span class="UserRoleIndicator UserRoleIndicator--filled is-green u-lSpace">OWNER</span>
|
||||
<% } else if (isAdmin) { %>
|
||||
<span class="UserRoleIndicator UserRoleIndicator--filled is-grey u-lSpace">ADMIN</span>
|
||||
<% } %>
|
||||
<span class="UserRoleIndicator u-altTextColor u-lSpace">
|
||||
<% if (isViewer) { %>
|
||||
VIEWER
|
||||
<% } else { %>
|
||||
BUILDER
|
||||
<% } %>
|
||||
</span>
|
||||
</div>
|
||||
<h4 class="OrganizationList-userInfoSubtitle u-ellipsLongText" title="<%- user_email %>"><%- user_email %></h4>
|
||||
</div>
|
||||
<ul class="OrganizationList-userInfoData u-upperCase u-altTextColor">
|
||||
<li class="CDB-Text CDB-Size-small u-rSpace--xl u-flex u-alignCenter">
|
||||
<i class="CDB-IconFont CDB-IconFont-map CDB-Size-medium u-rSpace"></i>
|
||||
<span><%- maps_count %></span>
|
||||
</li>
|
||||
<li class="CDB-Text CDB-Size-small u-flex u-alignCenter">
|
||||
<i class="CDB-IconFont CDB-IconFont-rows CDB-Size-medium u-rSpace"></i>
|
||||
<span><%- table_count %></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% if (editable) { %>
|
||||
</a>
|
||||
<% } else { %>
|
||||
</span>
|
||||
<% } %>
|
||||
@@ -0,0 +1,37 @@
|
||||
const CoreView = require('backbone/core-view');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
const template = require('./organization-users-footer.tpl');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'configModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
className: 'Form-footer',
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.$el.html(
|
||||
template({
|
||||
seats: this.model.get('seats'),
|
||||
users: this.options.organizationUsers.totalCount(),
|
||||
newUserUrl: this.model.viewUrl().create(),
|
||||
upgradeUrl: window.upgrade_url,
|
||||
customHosted: this._configModel.isHosted()
|
||||
})
|
||||
);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.model, 'change:total_users', this.render);
|
||||
this.listenTo(this.options.organizationUsers, 'sync', this.render);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
<% if (seats > users) { %>
|
||||
<% if ((seats - users) < 5) { %>
|
||||
<p class="CDB-Text Form-footerText">
|
||||
<i class="CDB-IconFont CDB-IconFont-info Form-footerIcon"></i>
|
||||
You are near your seats limit. Consider to <a href="<%- upgradeUrl %>">upgrade your account</a>.
|
||||
</p>
|
||||
<% } else { %>
|
||||
<em></em>
|
||||
<% } %>
|
||||
<% } else if (!customHosted) { %>
|
||||
<p class="CDB-Text Form-footerText">
|
||||
<i class="CDB-IconFont CDB-IconFont-info Form-footerIcon"></i>
|
||||
<a href="mailto:sales@carto.com">Contact us</a> if you have any questions.
|
||||
</p>
|
||||
<a href="<%- upgradeUrl %>" class="CDB-Text Button Button--positive"><span>Upgrade account</span></a>
|
||||
<% } %>
|
||||
@@ -0,0 +1,70 @@
|
||||
const $ = require('jquery');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
const PaginationView = require('builder/components/pagination/pagination-view.js');
|
||||
const OrganizationUserView = require('./organization-user-view');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'userModel',
|
||||
'organization',
|
||||
'paginationModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.empty();
|
||||
|
||||
// Users list
|
||||
const $ul = $('<ul>').addClass('OrganizationList');
|
||||
this.$el.append($ul);
|
||||
|
||||
let totalPer = 0;
|
||||
this.collection.each(function (user) {
|
||||
// Calculations to create organization user bars
|
||||
const userPer = (user.get('quota_in_bytes') * 100) / this._organization.get('quota_in_bytes');
|
||||
const usedPer = (user.get('db_size_in_bytes') * 100) / this._organization.get('quota_in_bytes');
|
||||
user.organization = this._organization;
|
||||
|
||||
const view = new OrganizationUserView({
|
||||
model: user,
|
||||
isOwner: user.isOrgOwner(),
|
||||
isAdmin: user.isOrgAdmin(),
|
||||
isViewer: user.get('viewer'),
|
||||
editable: this._userModel.isOrgOwner() || this._userModel.id === user.id || !user.isOrgAdmin(),
|
||||
userPer: userPer,
|
||||
usedPer: usedPer,
|
||||
totalPer: totalPer,
|
||||
url: this._organization.viewUrl().edit(user)
|
||||
});
|
||||
|
||||
$ul.append(view.render().el);
|
||||
this.addView(view);
|
||||
|
||||
totalPer = totalPer + userPer;
|
||||
}, this);
|
||||
|
||||
// Paginator
|
||||
const $paginatorWrapper = $('<div>').addClass('OrganizationList-paginator');
|
||||
this.$el.append($paginatorWrapper);
|
||||
const paginationView = new PaginationView({
|
||||
model: this._paginationModel
|
||||
});
|
||||
$paginatorWrapper.append(paginationView.render().el);
|
||||
this.addView(paginationView);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.collection, 'sync', this.render);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,275 @@
|
||||
const $ = require('jquery');
|
||||
const CoreView = require('backbone/core-view');
|
||||
const Utils = require('builder/helpers/utils');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
const ModalsServiceModel = require('builder/components/modals/modals-service-model');
|
||||
const PaginationModel = require('builder/components/pagination/pagination-model');
|
||||
const template = require('./organization-users.tpl');
|
||||
const OrganizationUsersFooterView = require('./organization-users-footer-view');
|
||||
const DropdownAdminView = require('dashboard/components/dropdown/dropdown-admin-view');
|
||||
const PagedSearchModel = require('dashboard/data/paged-search-model');
|
||||
const TabPane = require('dashboard/components/tabpane/tabpane');
|
||||
const InviteUsersDialogView = require('../invite-users/invite-users-dialog-view');
|
||||
const addUsersTemplate = require('./add-users.tpl');
|
||||
const OrganizationUsersListView = require('./organization-users-list-view');
|
||||
const loadingView = require('builder/components/loading/render-loading');
|
||||
const noResultsTemplate = require('builder/components/no-results/no-results.tpl');
|
||||
const errorTemplate = require('dashboard/views/data-library/content/error-template.tpl');
|
||||
const ViewFactory = require('builder/components/view-factory');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'userModel',
|
||||
'configModel',
|
||||
'organization',
|
||||
'organizationUsers'
|
||||
];
|
||||
|
||||
/**
|
||||
* Organization users content, list, pagination,
|
||||
* form, footer...
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
events: {
|
||||
'click .js-addUserOptions': '_openAddUserDropdown',
|
||||
'click .js-search-link': '_onSearchClick',
|
||||
'click .js-clean-search': '_onCleanSearchClick',
|
||||
'keydown .js-search-input': '_onKeyDown',
|
||||
'submit .js-search-form': 'killEvent'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._modals = new ModalsServiceModel();
|
||||
|
||||
this.pagedSearchModel = new PagedSearchModel({
|
||||
per_page: 50,
|
||||
order: 'username'
|
||||
});
|
||||
|
||||
this.paginationModel = new PaginationModel({
|
||||
current_page: this.pagedSearchModel.get('page'),
|
||||
total_count: this._organizationUsers.totalCount(),
|
||||
per_page: this.pagedSearchModel.get('per_page')
|
||||
});
|
||||
|
||||
// Include current user in fetch results
|
||||
this._organizationUsers.excludeCurrentUser(false);
|
||||
|
||||
this._initBinds();
|
||||
|
||||
this.pagedSearchModel.fetch(this._organizationUsers);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
|
||||
this.$el.html(
|
||||
template({
|
||||
seats: this._organization.get('seats'),
|
||||
assigned_seats: this._organization.get('assigned_seats'),
|
||||
viewer_seats: this._organization.get('viewer_seats'),
|
||||
assigned_viewer_seats: this._organization.get('assigned_viewer_seats'),
|
||||
newUserUrl: this._organization.viewUrl().create()
|
||||
})
|
||||
);
|
||||
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._organizationUsers, 'fetching', function () {
|
||||
this._panes && this._panes.active('loading');
|
||||
});
|
||||
|
||||
this.listenTo(this._organizationUsers, 'error', function (error) {
|
||||
// Old requests can be stopped, so aborted requests are not
|
||||
// considered as an error
|
||||
if (!error || (error && error.statusText !== 'abort')) {
|
||||
this._panes.active('error');
|
||||
}
|
||||
});
|
||||
|
||||
this.listenTo(this._organizationUsers, 'sync', function (collection) {
|
||||
const total = collection.totalCount();
|
||||
|
||||
this.paginationModel.set({
|
||||
total_count: total,
|
||||
current_page: this.pagedSearchModel.get('page')
|
||||
});
|
||||
this._panes && this._panes.active(total > 0 ? 'users' : 'no_results');
|
||||
});
|
||||
|
||||
this.listenTo(this._organizationUsers, 'sync error loading', function (collection) {
|
||||
this[this.pagedSearchModel.get('q') ? '_showCleanSearchButton' : '_hideCleanSearchButton']();
|
||||
});
|
||||
|
||||
this.listenTo(this.paginationModel, 'change:current_page', function (model) {
|
||||
const newPage = model.get('current_page');
|
||||
|
||||
this.pagedSearchModel.set('page', newPage);
|
||||
this.pagedSearchModel.fetch(this._organizationUsers);
|
||||
});
|
||||
|
||||
$(document).on('click', '.js-inviteUser', this._onInviteClick.bind(this));
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
this._panes = new TabPane({
|
||||
el: this.$('.js-organizationUsersPanes')
|
||||
});
|
||||
this.addView(this._panes);
|
||||
|
||||
this._panes.addTab('users',
|
||||
new OrganizationUsersListView({
|
||||
organization: this._organization,
|
||||
collection: this._organizationUsers,
|
||||
paginationModel: this.paginationModel,
|
||||
userModel: this._userModel
|
||||
}).render()
|
||||
);
|
||||
|
||||
this._panes.addTab('error', ViewFactory.createByHTML(errorTemplate({
|
||||
msg: ''
|
||||
})).render());
|
||||
|
||||
this._panes.addTab('no_results', ViewFactory.createByHTML(noResultsTemplate({
|
||||
icon: 'CDB-IconFont-defaultUser',
|
||||
title: 'Oh! No results',
|
||||
desc: 'Unfortunately we haven\'t found any user with these parameters'
|
||||
})).render());
|
||||
|
||||
this._panes.addTab('loading', ViewFactory.createByHTML(loadingView({
|
||||
title: 'Getting users...'
|
||||
})).render());
|
||||
|
||||
// Form footer
|
||||
const footer = new OrganizationUsersFooterView({
|
||||
model: this._organization,
|
||||
organizationUsers: this._organizationUsers,
|
||||
configModel: this._configModel
|
||||
});
|
||||
this.addView(footer);
|
||||
this.$el.append(footer.render().el);
|
||||
|
||||
let activePane = 'loading';
|
||||
|
||||
if (this._organizationUsers.totalCount() === 0) {
|
||||
activePane = 'no_results';
|
||||
} else if (this._organizationUsers.totalCount() > 0) {
|
||||
activePane = 'users';
|
||||
}
|
||||
|
||||
this._panes.active(activePane);
|
||||
},
|
||||
|
||||
_openAddUserDropdown: function (event) {
|
||||
if (this._dropdownView) this._dropdownView.clean();
|
||||
|
||||
this._dropdownView = new DropdownAdminView({
|
||||
className: 'Dropdown border',
|
||||
target: $(event.target),
|
||||
width: 120,
|
||||
template: addUsersTemplate,
|
||||
vertical_position: 'down',
|
||||
horizontal_position: 'right',
|
||||
createUrl: this._organization.viewUrl().create(),
|
||||
tick: 'right'
|
||||
});
|
||||
|
||||
$('body').append(this._dropdownView.render().el);
|
||||
|
||||
this._dropdownView.on('onDropdownHidden', function () {
|
||||
this._dropdownView.clean();
|
||||
}, this);
|
||||
|
||||
this._dropdownView.open(event);
|
||||
},
|
||||
|
||||
_showCleanSearchButton: function () {
|
||||
this.$('.js-clean-search').show();
|
||||
},
|
||||
|
||||
_hideCleanSearchButton: function () {
|
||||
this.$('.js-clean-search').hide();
|
||||
},
|
||||
|
||||
_focusSearchInput: function () {
|
||||
var $searchInput = this._$searchInput();
|
||||
$searchInput.focus().val($searchInput.val());
|
||||
},
|
||||
|
||||
_onSearchClick: function (e) {
|
||||
if (e) this.killEvent(e);
|
||||
this._$searchInput().focus();
|
||||
},
|
||||
|
||||
_onCleanSearchClick: function (ev) {
|
||||
this.killEvent(ev);
|
||||
this._cleanSearch();
|
||||
},
|
||||
|
||||
_onKeyDown: function (ev) {
|
||||
var enterPressed = (ev.keyCode === $.ui.keyCode.ENTER);
|
||||
var escapePressed = (ev.keyCode === $.ui.keyCode.ESCAPE);
|
||||
if (enterPressed) {
|
||||
this.killEvent(ev);
|
||||
this._submitSearch();
|
||||
} else if (escapePressed) {
|
||||
this.killEvent(ev);
|
||||
if (this.pagedSearchModel.get('q')) {
|
||||
this._cleanSearch();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_submitSearch: function (e) {
|
||||
var search = this._$searchInput().val().trim();
|
||||
this.pagedSearchModel.set({
|
||||
q: Utils.stripHTML(search),
|
||||
page: 1
|
||||
});
|
||||
|
||||
this.pagedSearchModel.fetch(this._organizationUsers);
|
||||
},
|
||||
|
||||
_$searchInput: function () {
|
||||
return this.$('.js-search-input');
|
||||
},
|
||||
|
||||
_cleanSearch: function () {
|
||||
this._$searchInput().val('');
|
||||
this.pagedSearchModel.set({
|
||||
q: '',
|
||||
page: 1
|
||||
});
|
||||
|
||||
this.pagedSearchModel.fetch(this._organizationUsers);
|
||||
},
|
||||
|
||||
_onInviteClick: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
this._dropdownView.clean();
|
||||
|
||||
this._modals.create(modalModel =>
|
||||
new InviteUsersDialogView({
|
||||
configModel: this._configModel,
|
||||
organization: this._organization,
|
||||
organizationUsers: this._organizationUsers,
|
||||
modalModel
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
clean: function () {
|
||||
this._organizationUsers.restoreExcludeCurrentUser();
|
||||
$(document).off('click', '.js-inviteUser', this._onInviteClick.bind(this));
|
||||
CoreView.prototype.clean.apply(this);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
<div class="OrganizationSearch">
|
||||
<div class="OrganizationSearch-Item">
|
||||
<button class="OrganizationSearch-FormButton js-search-link u-alignCenter CDB-Text CDB-Size-medium u-actionTextColor">
|
||||
<i class="CDB-IconFont CDB-IconFont-lens"></i>Search
|
||||
</button>
|
||||
</div>
|
||||
<div class="OrganizationSearch-Item">
|
||||
<form class="OrganizationSearch-Form js-search-form" action="#">
|
||||
<input class="OrganizationSearch-FormInput CDB-Text CDB-Size-medium u-secondaryTextColor js-search-input" type="text" placeholder="Search by username or email" />
|
||||
<button type="button" class="CDB-Text CDB-Size-large u-actionTextColor OrganizationSearch-FormButton--clean js-clean-search" style="display: none;">x</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="u-flex u-alignCenter CDB-Text CDB-Size-small u-secondaryTextColor">
|
||||
Builder (<%- assigned_seats %>/<%- seats %>) - Viewer (<%- assigned_viewer_seats %>/<%- viewer_seats %>)
|
||||
</div>
|
||||
<div class="OrganizationSearch-Item">
|
||||
<button class="CDB-Button CDB-Button--secondary CDB-Button--small js-addUserOptions">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase">
|
||||
Add users
|
||||
<i class="Button-arrowMenu CDB-IconFont CDB-IconFont-caretDown"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="CDB-Text js-organizationUsersPanes"></div>
|
||||
Reference in New Issue
Block a user