aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/project-permissions
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2015-08-20 15:39:03 +0200
committerStas Vilchik <vilchiks@gmail.com>2015-08-24 09:22:49 +0200
commit7a65a44016c32c5656e7aa5f0e174e670b6849b0 (patch)
treeea8840f4bc19efd99d78a917e797bc1c2902e98e /server/sonar-web/src/main/js/apps/project-permissions
parentc007b1899708a5cb4dce68087ed33a0c36551877 (diff)
downloadsonarqube-7a65a44016c32c5656e7aa5f0e174e670b6849b0.tar.gz
sonarqube-7a65a44016c32c5656e7aa5f0e174e670b6849b0.zip
SONAR-6797 rewrite project permissions page
Diffstat (limited to 'server/sonar-web/src/main/js/apps/project-permissions')
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/app.jsx13
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/groups-view.js43
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/main.jsx80
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/permissions-footer.jsx20
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/permissions-header.jsx26
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/permissions.jsx23
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/project.jsx66
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/search.jsx34
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-groups.hbs10
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-users.hbs10
-rw-r--r--server/sonar-web/src/main/js/apps/project-permissions/users-view.js43
11 files changed, 368 insertions, 0 deletions
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/app.jsx b/server/sonar-web/src/main/js/apps/project-permissions/app.jsx
new file mode 100644
index 00000000000..28dc73b7f42
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-permissions/app.jsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import Main from './main';
+
+const $ = jQuery;
+
+export default {
+ start(options) {
+ window.requestMessages().done(() => {
+ var el = document.querySelector(options.el);
+ React.render(<Main/>, el);
+ });
+ }
+};
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/groups-view.js b/server/sonar-web/src/main/js/apps/project-permissions/groups-view.js
new file mode 100644
index 00000000000..ba98ca7e525
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-permissions/groups-view.js
@@ -0,0 +1,43 @@
+define([
+ 'components/common/modals',
+ 'components/common/select-list',
+ './templates'
+], function (Modal) {
+
+ return Modal.extend({
+ template: Templates['project-permissions-groups'],
+
+ onRender: function () {
+ this._super();
+ new window.SelectList({
+ el: this.$('#project-permissions-groups'),
+ width: '100%',
+ readOnly: false,
+ focusSearch: false,
+ format: function (item) {
+ return item.name;
+ },
+ queryParam: 'q',
+ searchUrl: baseUrl + '/api/permissions/groups?ps=100&permission=' + this.options.permission + '&projectId=' + this.options.project,
+ selectUrl: baseUrl + '/api/permissions/add_group',
+ deselectUrl: baseUrl + '/api/permissions/remove_group',
+ extra: {
+ permission: this.options.permission,
+ projectId: this.options.project
+ },
+ selectParameter: 'groupName',
+ selectParameterValue: 'name',
+ parse: function (r) {
+ this.more = false;
+ return r.groups;
+ }
+ });
+ },
+
+ onDestroy: function () {
+ this.options.refresh && this.options.refresh();
+ this._super();
+ }
+ });
+
+});
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/main.jsx b/server/sonar-web/src/main/js/apps/project-permissions/main.jsx
new file mode 100644
index 00000000000..96b77f7cb72
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-permissions/main.jsx
@@ -0,0 +1,80 @@
+import React from 'react';
+import Permissions from './permissions';
+import PermissionsFooter from './permissions-footer';
+import Search from './search';
+
+let $ = jQuery;
+
+export default React.createClass({
+ getInitialState() {
+ return { permissions: [], projects: [], total: 0 };
+ },
+
+ componentDidMount() {
+ this.requestPermissions();
+ },
+
+ mergePermissionsToProjects(projects, basePermissions) {
+ return projects.map(project => {
+ // it's important to keep the order of the project permissions the same as the order of base permissions
+ let permissions = basePermissions.map(basePermission => {
+ let projectPermission = _.findWhere(project.permissions, { key: basePermission.key });
+ if (!projectPermission) {
+ throw new Error(`Project "${project.name} [${project.key}]" doesn't have permission "${basePermission.key}"`);
+ }
+ return _.extend({}, basePermission, projectPermission);
+ });
+ return _.extend({}, project, { permissions: permissions });
+ });
+ },
+
+ requestPermissions(page = 1, query = '') {
+ let url = `${window.baseUrl}/api/permissions/search_project_permissions`;
+ let data = { p: page, q: query };
+ $.get(url, data).done(r => {
+ let projects = this.mergePermissionsToProjects(r.projects, r.permissions);
+ if (page > 1) {
+ projects = [].concat(this.state.projects, projects);
+ }
+ this.setState({
+ projects: projects,
+ permissions: r.permissions,
+ total: r.paging.total,
+ page: r.paging.pageIndex,
+ query: query
+ });
+ });
+ },
+
+ loadMore() {
+ this.requestPermissions(this.state.page + 1, this.state.query);
+ },
+
+ search(query) {
+ this.requestPermissions(1, query);
+ },
+
+ render() {
+ return (
+ <div className="page">
+ <header id="project-permissions-header" className="page-header">
+ <h1 className="page-title">{window.t('roles.page')}</h1>
+ <p className="page-description">{window.t('roles.page.description2')}</p>
+ </header>
+
+ <Search
+ search={this.search}/>
+
+ <Permissions
+ projects={this.state.projects}
+ permissions={this.state.permissions}
+ refresh={this.requestPermissions}/>
+
+ <PermissionsFooter
+ count={this.state.projects.length}
+ total={this.state.total}
+ loadMore={this.loadMore}/>
+ </div>
+ );
+ }
+});
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/permissions-footer.jsx b/server/sonar-web/src/main/js/apps/project-permissions/permissions-footer.jsx
new file mode 100644
index 00000000000..f4913a753c2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-permissions/permissions-footer.jsx
@@ -0,0 +1,20 @@
+import React from 'react';
+
+export default React.createClass({
+ propTypes:{
+ count: React.PropTypes.number.isRequired,
+ total: React.PropTypes.number.isRequired,
+ loadMore: React.PropTypes.func.isRequired
+ },
+
+ render() {
+ let hasMore = this.props.total > this.props.count;
+ let loadMoreLink = <a onClick={this.props.loadMore} className="spacer-left" href="#">show more</a>;
+ return (
+ <footer className="spacer-top note text-center">
+ {this.props.count}/{this.props.total} shown
+ {hasMore ? loadMoreLink : null}
+ </footer>
+ );
+ }
+});
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/permissions-header.jsx b/server/sonar-web/src/main/js/apps/project-permissions/permissions-header.jsx
new file mode 100644
index 00000000000..a68cca21fc2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-permissions/permissions-header.jsx
@@ -0,0 +1,26 @@
+import React from 'react';
+
+export default React.createClass({
+ propTypes: {
+ permissions: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
+ },
+
+ render() {
+ let cellWidth = (80 / this.props.permissions.length) + '%';
+ let cells = this.props.permissions.map(p => {
+ return (
+ <th key={p.key} style={{ width: cellWidth }}>
+ {p.name}<br/><span className="small">{p.description}</span>
+ </th>
+ );
+ });
+ return (
+ <thead>
+ <tr>
+ <th style={{ width: '20%' }}>&nbsp;</th>
+ {cells}
+ </tr>
+ </thead>
+ );
+ }
+});
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/permissions.jsx b/server/sonar-web/src/main/js/apps/project-permissions/permissions.jsx
new file mode 100644
index 00000000000..4638c209651
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-permissions/permissions.jsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import PermissionsHeader from './permissions-header';
+import Project from './project';
+
+export default React.createClass({
+ propTypes:{
+ projects: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
+ permissions: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
+ refresh: React.PropTypes.func.isRequired
+ },
+
+ render() {
+ let projects = this.props.projects.map(p => {
+ return <Project key={p.uuid} project={p} refresh={this.props.refresh}/>
+ });
+ return (
+ <table id="projects" className="data zebra">
+ <PermissionsHeader permissions={this.props.permissions}/>
+ <tbody>{projects}</tbody>
+ </table>
+ );
+ }
+});
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/project.jsx b/server/sonar-web/src/main/js/apps/project-permissions/project.jsx
new file mode 100644
index 00000000000..31c03a1572f
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-permissions/project.jsx
@@ -0,0 +1,66 @@
+import React from 'react';
+import UsersView from './users-view';
+import GroupsView from './groups-view';
+import {getProjectUrl} from '../../helpers/Url';
+
+export default React.createClass({
+ propTypes: {
+ project: React.PropTypes.object.isRequired,
+ refresh: React.PropTypes.func.isRequired
+ },
+
+ showGroups(permission, e) {
+ e.preventDefault();
+ new GroupsView({
+ permission: permission,
+ project: this.props.project.uuid,
+ refresh: this.props.refresh
+ }).render();
+ },
+
+ showUsers(permission, e) {
+ e.preventDefault();
+ new UsersView({
+ permission: permission,
+ project: this.props.project.uuid,
+ refresh: this.props.refresh
+ }).render();
+ },
+
+ render() {
+ let permissions = this.props.project.permissions.map(p => {
+ return (
+ <td key={p.key}>
+ <table>
+ <tr>
+ <td className="spacer-right">Users</td>
+ <td className="spacer-left bordered-left">{p.usersCount}</td>
+ <td className="spacer-left">
+ <a onClick={this.showUsers.bind(this, p.key)} className="icon-bullet-list" title="Update Users"
+ data-toggle="tooltip" href="#"></a>
+ </td>
+ </tr>
+ <tr>
+ <td className="spacer-right">Groups</td>
+ <td className="spacer-left bordered-left">{p.groupsCount}</td>
+ <td className="spacer-left">
+ <a onClick={this.showGroups.bind(this, p.key)} className="icon-bullet-list" title="Update Users"
+ data-toggle="tooltip" href="#"></a>
+ </td>
+ </tr>
+ </table>
+ </td>
+ );
+ });
+ return (
+ <tr>
+ <td>
+ <strong>
+ <a href={getProjectUrl(this.props.project.key)}>{this.props.project.name}</a>
+ </strong>
+ </td>
+ {permissions}
+ </tr>
+ );
+ }
+});
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/search.jsx b/server/sonar-web/src/main/js/apps/project-permissions/search.jsx
new file mode 100644
index 00000000000..3506d78834b
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-permissions/search.jsx
@@ -0,0 +1,34 @@
+import React from 'react';
+
+export default React.createClass({
+ propTypes: {
+ search: React.PropTypes.func.isRequired
+ },
+
+ componentWillMount: function () {
+ this.search = _.debounce(this.search, 250);
+ },
+
+ onSubmit(e) {
+ e.preventDefault();
+ this.search();
+ },
+
+ search() {
+ let q = React.findDOMNode(this.refs.input).value;
+ this.props.search(q);
+ },
+
+ render() {
+ return (
+ <div className="panel panel-vertical bordered-bottom spacer-bottom">
+ <form onSubmit={this.onSubmit} className="search-box">
+ <button className="search-box-submit button-clean">
+ <i className="icon-search"></i>
+ </button>
+ <input onChange={this.search} ref="input" className="search-box-input" type="search" placeholder="Search"/>
+ </form>
+ </div>
+ );
+ }
+});
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-groups.hbs b/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-groups.hbs
new file mode 100644
index 00000000000..c5f551e3682
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-groups.hbs
@@ -0,0 +1,10 @@
+<div class="modal-head">
+ <h2>Update Groups</h2>
+</div>
+<div class="modal-body">
+ <div class="js-modal-messages"></div>
+ <div id="project-permissions-groups"></div>
+</div>
+<div class="modal-foot">
+ <a href="#" class="js-modal-close">Done</a>
+</div>
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-users.hbs b/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-users.hbs
new file mode 100644
index 00000000000..acfd4eaf75d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-users.hbs
@@ -0,0 +1,10 @@
+<div class="modal-head">
+ <h2>Update Users</h2>
+</div>
+<div class="modal-body">
+ <div class="js-modal-messages"></div>
+ <div id="project-permissions-users"></div>
+</div>
+<div class="modal-foot">
+ <a href="#" class="js-modal-close">Done</a>
+</div>
diff --git a/server/sonar-web/src/main/js/apps/project-permissions/users-view.js b/server/sonar-web/src/main/js/apps/project-permissions/users-view.js
new file mode 100644
index 00000000000..6a715835ba7
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/project-permissions/users-view.js
@@ -0,0 +1,43 @@
+define([
+ 'components/common/modals',
+ 'components/common/select-list',
+ './templates'
+], function (Modal) {
+
+ return Modal.extend({
+ template: Templates['project-permissions-users'],
+
+ onRender: function () {
+ this._super();
+ new window.SelectList({
+ el: this.$('#project-permissions-users'),
+ width: '100%',
+ readOnly: false,
+ focusSearch: false,
+ format: function (item) {
+ return item.name + '<br><span class="note">' + item.login + '</span>';
+ },
+ queryParam: 'q',
+ searchUrl: baseUrl + '/api/permissions/users?ps=100&permission=' + this.options.permission + '&projectId=' + this.options.project,
+ selectUrl: baseUrl + '/api/permissions/add_user',
+ deselectUrl: baseUrl + '/api/permissions/remove_user',
+ extra: {
+ permission: this.options.permission,
+ projectId: this.options.project
+ },
+ selectParameter: 'login',
+ selectParameterValue: 'login',
+ parse: function (r) {
+ this.more = false;
+ return r.users;
+ }
+ });
+ },
+
+ onDestroy: function () {
+ this.options.refresh && this.options.refresh();
+ this._super();
+ }
+ });
+
+});