diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2015-08-20 15:39:03 +0200 |
---|---|---|
committer | Stas Vilchik <vilchiks@gmail.com> | 2015-08-24 09:22:49 +0200 |
commit | 7a65a44016c32c5656e7aa5f0e174e670b6849b0 (patch) | |
tree | ea8840f4bc19efd99d78a917e797bc1c2902e98e /server/sonar-web/src/main/js/apps/project-permissions | |
parent | c007b1899708a5cb4dce68087ed33a0c36551877 (diff) | |
download | sonarqube-7a65a44016c32c5656e7aa5f0e174e670b6849b0.tar.gz sonarqube-7a65a44016c32c5656e7aa5f0e174e670b6849b0.zip |
SONAR-6797 rewrite project permissions page
Diffstat (limited to 'server/sonar-web/src/main/js/apps/project-permissions')
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%' }}> </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(); + } + }); + +}); |