Initial commit

This commit is contained in:
zhongjin
2020-06-15 10:58:47 +08:00
commit 4f1dfe7564
8590 changed files with 1516878 additions and 0 deletions

View File

@@ -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>

View File

@@ -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;
}
});

View File

@@ -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>
<% } %>

View File

@@ -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);
}
});

View File

@@ -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>
<% } %>

View File

@@ -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);
}
});

View File

@@ -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);
}
});

View File

@@ -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>