import _ from 'underscore';
import { getJSON, post } from '../helpers/request';
+const PAGE_SIZE = 100;
+
function request (options) {
return $.ajax(options);
}
-function typeError (method, message) {
- throw new TypeError(`permissions#${method}: ${message}`);
-}
-
-export function getUsers (data) {
- const url = window.baseUrl + '/api/permissions/users';
- return request({ type: 'GET', url, data });
+export function getPermissionUsers (data) {
+ const url = '/api/permissions/users';
+ return getJSON(url, data);
}
-export function grantToUser (permission, user, project) {
- if (typeof permission !== 'string' || !permission.length) {
- return typeError('grantToUser', 'please provide permission');
- }
- if (typeof user !== 'string' || !user.length) {
- return typeError('grantToUser', 'please provide user login');
- }
-
- const url = window.baseUrl + '/api/permissions/add_user';
- const data = { permission, login: user };
- if (project) {
- data.projectId = project;
+export function grantPermissionToUser (projectKey, login, permission) {
+ const url = '/api/permissions/add_user';
+ const data = { login, permission };
+ if (projectKey) {
+ data.projectKey = projectKey;
}
- return request({ type: 'POST', url, data });
+ return post(url, data);
}
-export function revokeFromUser (permission, user, project) {
- if (typeof permission !== 'string' || !permission.length) {
- return typeError('revokeFromUser', 'please provide permission');
- }
- if (typeof user !== 'string' || !user.length) {
- return typeError('revokeFromUser', 'please provide user login');
- }
-
- const url = window.baseUrl + '/api/permissions/remove_user';
- const data = { permission, login: user };
- if (project) {
- data.projectId = project;
+export function revokePermissionFromUser (projectKey, login, permission) {
+ const url = '/api/permissions/remove_user';
+ const data = { login, permission };
+ if (projectKey) {
+ data.projectKey = projectKey;
}
- return request({ type: 'POST', url, data });
+ return post(url, data);
}
-export function getGroups (data) {
- const url = window.baseUrl + '/api/permissions/groups';
- return request({ type: 'GET', url, data });
+export function getPermissionGroups (data) {
+ const url = '/api/permissions/groups';
+ return getJSON(url, data);
}
-export function grantToGroup (permission, group, project) {
- if (typeof permission !== 'string' || !permission.length) {
- return typeError('grantToGroup', 'please provide permission');
- }
- if (typeof group !== 'string' || !group.length) {
- return typeError('grantToGroup', 'please provide group name');
+export function grantPermissionToGroup (projectKey, groupName, permission) {
+ const url = '/api/permissions/add_group';
+ const data = { groupName, permission };
+ if (projectKey) {
+ data.projectKey = projectKey;
}
-
- const url = window.baseUrl + '/api/permissions/add_group';
- const data = { permission, groupName: group };
- if (project) {
- data.projectId = project;
- }
- return request({ type: 'POST', url, data });
+ return post(url, data);
}
-export function revokeFromGroup (permission, group, project) {
- if (typeof permission !== 'string' || !permission.length) {
- return typeError('revokeFromGroup', 'please provide permission');
+export function revokePermissionFromGroup (projectKey, groupName, permission) {
+ const url = '/api/permissions/remove_group';
+ const data = { groupName, permission };
+ if (projectKey) {
+ data.projectKey = projectKey;
}
- if (typeof group !== 'string' || !group.length) {
- return typeError('revokeFromGroup', 'please provide group name');
- }
-
- const url = window.baseUrl + '/api/permissions/remove_group';
- const data = { permission, groupName: group };
- if (project) {
- data.projectId = project;
- }
- return request({ type: 'POST', url, data });
+ return post(url, data);
}
/**
return post(url, data);
}
-export function applyTemplateToProject (options) {
- const url = window.baseUrl + '/api/permissions/apply_template';
- return request(_.extend({ type: 'POST', url }, options));
+export function applyTemplateToProject (data) {
+ const url = '/api/permissions/apply_template';
+ return post(url, data);
}
export function bulkApplyTemplateToProject (options) {
const data = { templateName, permission };
return post(url, data);
}
+
+export function getPermissionsUsersForComponent (projectKey, query = '', permission = null) {
+ const url = '/api/permissions/users';
+ const data = { projectKey, ps: PAGE_SIZE };
+ if (query) {
+ data.q = query;
+ }
+ if (permission) {
+ data.permission = permission;
+ }
+ return getJSON(url, data).then(r => r.users);
+}
+
+export function getPermissionsGroupsForComponent (projectKey, query = '', permission = null) {
+ const url = '/api/permissions/groups';
+ const data = { projectKey, ps: PAGE_SIZE };
+ if (query) {
+ data.q = query;
+ }
+ if (permission) {
+ data.permission = permission;
+ }
+ return getJSON(url, data).then(r => r.groups);
+}
+
+export function getGlobalPermissionsUsers (query = '', permission = null) {
+ const url = '/api/permissions/users';
+ const data = { ps: PAGE_SIZE };
+ if (query) {
+ data.q = query;
+ }
+ if (permission) {
+ data.permission = permission;
+ }
+ return getJSON(url, data).then(r => r.users);
+}
+
+export function getGlobalPermissionsGroups (query = '', permission = null) {
+ const url = '/api/permissions/groups';
+ const data = { ps: PAGE_SIZE };
+ if (query) {
+ data.q = query;
+ }
+ if (permission) {
+ data.permission = permission;
+ }
+ return getJSON(url, data).then(r => r.groups);
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import ReactDOM from 'react-dom';
-import Main from './main';
-
-window.sonarqube.appStarted.then(options => {
- const el = document.querySelector(options.el);
- ReactDOM.render(<Main/>, el);
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Modal from '../../components/common/modals';
-import Template from './templates/global-permissions-groups.hbs';
-import '../../components/SelectList';
-
-function getSearchUrl (permission, project) {
- let url = window.baseUrl + '/api/permissions/groups?ps=100&permission=' + permission;
- if (project) {
- url = url + '&projectId=' + project;
- }
- return url;
-}
-
-function getExtra (permission, project) {
- const extra = { permission };
- if (project) {
- extra.projectId = project;
- }
- return extra;
-}
-
-export default Modal.extend({
- template: Template,
-
- onRender () {
- Modal.prototype.onRender.apply(this, arguments);
- new window.SelectList({
- el: this.$('#global-permissions-groups'),
- width: '100%',
- readOnly: false,
- focusSearch: false,
- format (item) {
- return item.name;
- },
- queryParam: 'q',
- searchUrl: getSearchUrl(this.options.permission, this.options.project),
- selectUrl: window.baseUrl + '/api/permissions/add_group',
- deselectUrl: window.baseUrl + '/api/permissions/remove_group',
- extra: getExtra(this.options.permission, this.options.project),
- selectParameter: 'groupName',
- selectParameterValue: 'name',
- parse (r) {
- this.more = false;
- return r.groups;
- }
- });
- },
-
- onDestroy () {
- this.options.refresh();
- Modal.prototype.onDestroy.apply(this, arguments);
- }
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import React from 'react';
-import PermissionsList from './permissions-list';
-import { translate } from '../../helpers/l10n';
-
-export default React.createClass({
- getInitialState() {
- return { ready: false, permissions: [] };
- },
-
- componentDidMount() {
- this.requestPermissions();
- },
-
- requestPermissions() {
- const url = window.baseUrl + '/api/permissions/search_global_permissions';
- $.get(url).done(r => {
- this.setState({ ready: true, permissions: r.permissions });
- });
- },
-
- renderSpinner () {
- if (this.state.ready) {
- return null;
- }
- return <i className="spinner"/>;
- },
-
- render() {
- return (
- <div className="page">
- <header id="global-permissions-header" className="page-header">
- <h1 className="page-title">{translate('global_permissions.page')}</h1>
- {this.renderSpinner()}
- <p className="page-description">{translate('global_permissions.page.description')}</p>
- </header>
- <PermissionsList ready={this.state.ready} permissions={this.state.permissions}/>
- </div>
- );
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import PermissionsUsersGroupsMixin from './permission-users-groups-mixin';
-import GroupsView from './groups-view';
-
-export default React.createClass({
- mixins: [PermissionsUsersGroupsMixin],
-
- renderUpdateLink() {
- return (
- <a onClick={this.updateGroups}
- className="icon-bullet-list"
- title="Update Groups"
- data-toggle="tooltip"
- href="#"></a>
- );
- },
-
- renderItem(item) {
- return item.name;
- },
-
- renderTitle() {
- return 'Groups';
- },
-
- updateGroups(e) {
- e.preventDefault();
- new GroupsView({
- permission: this.props.permission.key,
- project: this.props.project,
- refresh: this.props.refresh
- }).render();
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-
-export default {
- propTypes: {
- permission: React.PropTypes.object.isRequired,
- max: React.PropTypes.number.isRequired,
- items: React.PropTypes.array,
- total: React.PropTypes.number,
- refresh: React.PropTypes.func.isRequired
- },
-
- renderNotDisplayed() {
- const notDisplayedCount = this.props.total - this.props.max;
- return notDisplayedCount > 0 ?
- <span className="note spacer-right" href="#">and {notDisplayedCount} more</span> : null;
- },
-
- renderItems() {
- const displayed = this.props.items.map(item => {
- return <li key={item.name} className="spacer-left little-spacer-bottom">{this.renderItem(item)}</li>;
- });
- return (
- <ul className="overflow-hidden bordered-left">
- {displayed}
- <li className="spacer-left little-spacer-bottom">
- {this.renderNotDisplayed()}
- {this.renderUpdateLink()}
- </li>
- </ul>
- );
- },
-
- renderCount() {
- return (
- <ul className="overflow-hidden bordered-left">
- <li className="spacer-left little-spacer-bottom">
- <span className="spacer-right">{this.props.total}</span>
- {this.renderUpdateLink()}
- </li>
- </ul>
- );
- },
-
- render() {
- return (
- <li className="abs-width-400">
- <div className="pull-left spacer-right">
- <strong>{this.renderTitle()}</strong>
- </div>
- {this.props.items ? this.renderItems() : this.renderCount()}
- </li>
- );
- }
-};
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import PermissionsUsersGroupsMixin from './permission-users-groups-mixin';
-import UsersView from './users-view';
-
-export default React.createClass({
- mixins: [PermissionsUsersGroupsMixin],
-
- renderUpdateLink() {
- return (
- <a onClick={this.updateUsers}
- className="icon-bullet-list"
- title="Update Users"
- data-toggle="tooltip"
- href="#"></a>
- );
- },
-
- renderItem(item) {
- return item.name;
- },
-
- renderTitle() {
- return 'Users';
- },
-
- updateUsers(e) {
- e.preventDefault();
- new UsersView({
- permission: this.props.permission.key,
- project: this.props.project,
- refresh: this.props.refresh
- }).render();
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import React from 'react';
-import PermissionUsers from './permission-users';
-import PermissionGroups from './permission-groups';
-
-// Maximum number of displayed groups
-const MAX_ITEMS = 3;
-
-export default React.createClass({
- propTypes: {
- permission: React.PropTypes.object.isRequired
- },
-
- getInitialState() {
- return {};
- },
-
- componentDidMount() {
- this.requestUsers();
- this.requestGroups();
- },
-
- requestUsers() {
- const url = window.baseUrl + '/api/permissions/users';
- const data = { permission: this.props.permission.key, ps: MAX_ITEMS };
- if (this.props.project) {
- data.projectId = this.props.project;
- }
- $.get(url, data).done(r => this.setState({ users: r.users, totalUsers: r.paging && r.paging.total }));
- },
-
- requestGroups() {
- const url = window.baseUrl + '/api/permissions/groups';
- const data = { permission: this.props.permission.key, ps: MAX_ITEMS };
- if (this.props.project) {
- data.projectId = this.props.project;
- }
- $.get(url, data).done(r => this.setState({ groups: r.groups, totalGroups: r.paging && r.paging.total }));
- },
-
- render() {
- return (
- <li className="panel panel-vertical" data-id={this.props.permission.key}>
- <h3>{this.props.permission.name}</h3>
- <p className="spacer-top" dangerouslySetInnerHTML={{ __html: this.props.permission.description }}/>
- <ul className="list-inline spacer-top">
- <PermissionUsers permission={this.props.permission}
- project={this.props.project}
- max={MAX_ITEMS}
- items={this.state.users}
- total={this.state.totalUsers || this.props.permission.usersCount}
- refresh={this.requestUsers}/>
- <PermissionGroups permission={this.props.permission}
- project={this.props.project}
- max={MAX_ITEMS}
- items={this.state.groups}
- total={this.state.totalGroups || this.props.permission.groupsCount}
- refresh={this.requestGroups}/>
- </ul>
- </li>
- );
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import classNames from 'classnames';
-import React from 'react';
-
-import Permission from './permission';
-
-export default React.createClass({
- propTypes: {
- permissions: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
- },
-
- renderPermissions() {
- return this.props.permissions.map(permission => {
- return <Permission key={permission.key} permission={permission} project={this.props.project}/>;
- });
- },
-
- render() {
- const className = classNames({ 'new-loading': !this.props.ready });
- return <ul id="global-permissions-list" className={className}>{this.renderPermissions()}</ul>;
- }
-});
+++ /dev/null
-<div class="modal-head">
- <h2>Update Groups</h2>
-</div>
-<div class="modal-body">
- <div class="js-modal-messages"></div>
- <div id="global-permissions-groups"></div>
-</div>
-<div class="modal-foot">
- <a href="#" class="js-modal-close" id="global-permissions-groups-done">Done</a>
-</div>
+++ /dev/null
-<div class="modal-head">
- <h2>Update Users</h2>
-</div>
-<div class="modal-body">
- <div class="js-modal-messages"></div>
- <div id="global-permissions-users"></div>
-</div>
-<div class="modal-foot">
- <a href="#" class="js-modal-close" id="global-permissions-users-done">Done</a>
-</div>
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Modal from '../../components/common/modals';
-import Template from './templates/global-permissions-users.hbs';
-import '../../components/SelectList';
-
-function getSearchUrl (permission, project) {
- let url = window.baseUrl + '/api/permissions/users?ps=100&permission=' + permission;
- if (project) {
- url = url + '&projectId=' + project;
- }
- return url;
-}
-
-function getExtra (permission, project) {
- const extra = { permission };
- if (project) {
- extra.projectId = project;
- }
- return extra;
-}
-
-export default Modal.extend({
- template: Template,
-
- onRender () {
- Modal.prototype.onRender.apply(this, arguments);
- new window.SelectList({
- el: this.$('#global-permissions-users'),
- width: '100%',
- readOnly: false,
- focusSearch: false,
- format (item) {
- return `${item.name}<br><span class="note">${item.login}</span>`;
- },
- queryParam: 'q',
- searchUrl: getSearchUrl(this.options.permission, this.options.project),
- selectUrl: window.baseUrl + '/api/permissions/add_user',
- deselectUrl: window.baseUrl + '/api/permissions/remove_user',
- extra: getExtra(this.options.permission, this.options.project),
- selectParameter: 'login',
- selectParameterValue: 'login',
- parse (r) {
- this.more = false;
- return r.users;
- }
- });
- },
-
- onDestroy () {
- this.options.refresh();
- Modal.prototype.onDestroy.apply(this, arguments);
- }
-});
-
}
.permissions-table > tbody > tr > td {
- border-bottom: 20px solid #fff !important;
+ border-bottom: 10px solid #fff !important;
}
.permissions-table .permission-column {
- width: 130px;
- white-space: nowrap;
+ width: 12%;
+ min-width: 112px;
}
.permissions-table .actions-column {
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { render } from 'react-dom';
+import { Provider } from 'react-redux';
+import App from './components/App';
+import configureStore from '../../../components/store/configureStore';
+import rootReducer from '../shared/store/rootReducer';
+
+window.sonarqube.appStarted.then(options => {
+ const el = document.querySelector(options.el);
+ const store = configureStore(rootReducer);
+ render((
+ <Provider store={store}>
+ <App/>
+ </Provider>
+ ), el);
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { connect } from 'react-redux';
+import SearchForm from '../../shared/components/SearchForm';
+import HoldersList from '../../shared/components/HoldersList';
+import {
+ loadHolders,
+ grantToUser,
+ revokeFromUser,
+ grantToGroup,
+ revokeFromGroup,
+ updateFilter,
+ updateQuery,
+ selectPermission
+} from '../store/actions';
+import {
+ getUsers,
+ getGroups,
+ getQuery,
+ getFilter,
+ getSelectedPermission
+} from '../../shared/store/rootReducer';
+import { translate } from '../../../../helpers/l10n';
+
+const PERMISSIONS_ORDER = [
+ 'admin',
+ 'profileadmin',
+ 'gateadmin',
+ 'shareDashboard',
+ 'scan',
+ 'provisioning'
+];
+
+class AllHoldersList extends React.Component {
+ componentDidMount () {
+ this.props.loadHolders();
+ }
+
+ handleToggleUser (user, permission) {
+ const hasPermission = user.permissions.includes(permission);
+
+ if (hasPermission) {
+ this.props.revokePermissionFromUser(user.login, permission);
+ } else {
+ this.props.grantPermissionToUser(user.login, permission);
+ }
+ }
+
+ handleToggleGroup (group, permission) {
+ const hasPermission = group.permissions.includes(permission);
+
+ if (hasPermission) {
+ this.props.revokePermissionFromGroup(group.name, permission);
+ } else {
+ this.props.grantPermissionToGroup(group.name, permission);
+ }
+ }
+
+ render () {
+ const permissions = PERMISSIONS_ORDER.map(p => ({
+ key: p,
+ name: translate('global_permissions', p),
+ description: translate('global_permissions', p, 'desc')
+ }));
+
+ return (
+ <HoldersList
+ permissions={permissions}
+ selectedPermission={this.props.selectedPermission}
+ users={this.props.users}
+ groups={this.props.groups}
+ onSelectPermission={this.props.onSelectPermission}
+ onToggleUser={this.handleToggleUser.bind(this)}
+ onToggleGroup={this.handleToggleGroup.bind(this)}>
+
+ <SearchForm
+ query={this.props.query}
+ filter={this.props.filter}
+ onSearch={this.props.onSearch}
+ onFilter={this.props.onFilter}/>
+
+ </HoldersList>
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ users: getUsers(state),
+ groups: getGroups(state),
+ query: getQuery(state),
+ filter: getFilter(state),
+ selectedPermission: getSelectedPermission(state)
+});
+
+const mapDispatchToProps = dispatch => ({
+ loadHolders: () => dispatch(loadHolders()),
+ onSearch: query => dispatch(updateQuery(query)),
+ onFilter: filter => dispatch(updateFilter(filter)),
+ onSelectPermission: permission => dispatch(selectPermission(permission)),
+ grantPermissionToUser: (login, permission) =>
+ dispatch(grantToUser(login, permission)),
+ revokePermissionFromUser: (login, permission) =>
+ dispatch(revokeFromUser(login, permission)),
+ grantPermissionToGroup: (groupName, permission) =>
+ dispatch(grantToGroup(groupName, permission)),
+ revokePermissionFromGroup: (groupName, permission) =>
+ dispatch(revokeFromGroup(groupName, permission))
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(AllHoldersList);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import PageHeader from './PageHeader';
+import AllHoldersList from './AllHoldersList';
+import PageError from '../../shared/components/PageError';
+import '../../styles.css';
+
+// TODO helmet
+
+export default class App extends React.Component {
+ render () {
+ return (
+ <div className="page page-limited">
+ <PageHeader/>
+ <PageError/>
+ <AllHoldersList/>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { connect } from 'react-redux';
+import { translate } from '../../../../helpers/l10n';
+import { loadHolders } from '../store/actions';
+import { isLoading } from '../../shared/store/rootReducer';
+
+class PageHeader extends React.Component {
+ static propTypes = {
+ loadHolders: React.PropTypes.func.isRequired,
+ loading: React.PropTypes.bool
+ };
+
+ static defaultProps = {
+ loading: false
+ };
+
+ render () {
+ return (
+ <header className="page-header">
+ <h1 className="page-title">
+ {translate('global_permissions.page')}
+ </h1>
+
+ {this.props.loading && (
+ <i className="spinner"/>
+ )}
+
+ <div className="page-description">
+ {translate('global_permissions.page.description')}
+ </div>
+ </header>
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ loading: isLoading(state)
+});
+
+const mapDispatchToProps = dispatch => ({
+ loadHolders: () => dispatch(loadHolders())
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(PageHeader);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as api from '../../../../api/permissions';
+import { parseError } from '../../../code/utils';
+import { raiseError } from '../../shared/store/actions';
+import {
+ getQuery,
+ getFilter,
+ getSelectedPermission
+} from '../../shared/store/rootReducer';
+
+export const loadHolders = () => (dispatch, getState) => {
+ const query = getQuery(getState());
+ const filter = getFilter(getState());
+ const selectedPermission = getSelectedPermission(getState());
+
+ dispatch({ type: 'REQUEST_HOLDERS', query });
+
+ const requests = [];
+
+ if (filter !== 'groups') {
+ requests.push(api.getGlobalPermissionsUsers(query, selectedPermission));
+ } else {
+ requests.push(Promise.resolve([]));
+ }
+
+ if (filter !== 'users') {
+ requests.push(api.getGlobalPermissionsGroups(query, selectedPermission));
+ } else {
+ requests.push(Promise.resolve([]));
+ }
+
+ return Promise.all(requests).then(responses => (
+ dispatch({
+ type: 'RECEIVE_HOLDERS_SUCCESS',
+ users: responses[0],
+ groups: responses[1],
+ query
+ })
+ )).catch(e => {
+ return parseError(e).then(message => dispatch(raiseError(message)));
+ });
+};
+
+export const updateQuery = (query = '') => dispatch => {
+ dispatch({ type: 'UPDATE_QUERY', query });
+ if (query.length === 0 || query.length > 2) {
+ dispatch(loadHolders());
+ }
+};
+
+export const updateFilter = filter => dispatch => {
+ dispatch({ type: 'UPDATE_FILTER', filter });
+ dispatch(loadHolders());
+};
+
+export const selectPermission = permission => (dispatch, getState) => {
+ const selectedPermission = getSelectedPermission(getState());
+ if (selectedPermission !== permission) {
+ dispatch({ type: 'SELECT_PERMISSION', permission });
+ } else {
+ dispatch({ type: 'SELECT_PERMISSION', permission: null });
+ }
+ dispatch(loadHolders());
+};
+
+export const grantToUser = (login, permission) => dispatch => {
+ api.grantPermissionToUser(null, login, permission).then(() => {
+ dispatch({ type: 'GRANT_PERMISSION_TO_USER', login, permission });
+ }).catch(e => {
+ return parseError(e).then(message => dispatch(raiseError(message)));
+ });
+};
+
+export const revokeFromUser = (login, permission) => dispatch => {
+ api.revokePermissionFromUser(null, login, permission).then(() => {
+ dispatch({ type: 'REVOKE_PERMISSION_TO_USER', login, permission });
+ }).catch(e => {
+ return parseError(e).then(message => dispatch(raiseError(message)));
+ });
+};
+
+export const grantToGroup = (groupName, permission) => dispatch => {
+ api.grantPermissionToGroup(null, groupName, permission).then(() => {
+ dispatch({
+ type: 'GRANT_PERMISSION_TO_GROUP',
+ groupName,
+ permission
+ });
+ }).catch(e => {
+ return parseError(e).then(message => dispatch(raiseError(message)));
+ });
+};
+
+export const revokeFromGroup = (groupName, permission) => dispatch => {
+ api.revokePermissionFromGroup(null, groupName, permission).then(() => {
+ dispatch({
+ type: 'REVOKE_PERMISSION_FROM_GROUP',
+ groupName,
+ permission
+ });
+ }).catch(e => {
+ return parseError(e).then(message => dispatch(raiseError(message)));
+ });
+};
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { render } from 'react-dom';
+import { Provider } from 'react-redux';
+import App from './components/App';
+import configureStore from '../../../components/store/configureStore';
+import rootReducer from '../shared/store/rootReducer';
+
+window.sonarqube.appStarted.then(options => {
+ const el = document.querySelector(options.el);
+ const store = configureStore(rootReducer);
+ render((
+ <Provider store={store}>
+ <App project={options.component}/>
+ </Provider>
+ ), el);
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { connect } from 'react-redux';
+import SearchForm from '../../shared/components/SearchForm';
+import HoldersList from '../../shared/components/HoldersList';
+import {
+ loadHolders,
+ grantToUser,
+ revokeFromUser,
+ grantToGroup,
+ revokeFromGroup,
+ updateQuery,
+ updateFilter,
+ selectPermission
+} from '../store/actions';
+import {
+ getUsers,
+ getGroups,
+ getQuery,
+ getFilter,
+ getSelectedPermission
+} from '../../shared/store/rootReducer';
+import { translate } from '../../../../helpers/l10n';
+
+export const PERMISSIONS_ORDER = [
+ 'user',
+ 'codeviewer',
+ 'issueadmin',
+ 'admin',
+ 'scan'
+];
+
+class AllHoldersList extends React.Component {
+ static propTypes = {
+ project: React.PropTypes.object.isRequired
+ };
+
+ componentDidMount () {
+ this.props.loadHolders(this.props.project.key);
+ }
+
+ handleSearch (query) {
+ this.props.onSearch(this.props.project.key, query);
+ }
+
+ handleFilter (filter) {
+ this.props.onFilter(this.props.project.key, filter);
+ }
+
+ handleToggleUser (user, permission) {
+ const hasPermission = user.permissions.includes(permission);
+
+ if (hasPermission) {
+ this.props.revokePermissionFromUser(
+ this.props.project.key,
+ user.login,
+ permission
+ );
+ } else {
+ this.props.grantPermissionToUser(
+ this.props.project.key,
+ user.login,
+ permission
+ );
+ }
+ }
+
+ handleToggleGroup (group, permission) {
+ const hasPermission = group.permissions.includes(permission);
+
+ if (hasPermission) {
+ this.props.revokePermissionFromGroup(
+ this.props.project.key,
+ group.name,
+ permission
+ );
+ } else {
+ this.props.grantPermissionToGroup(
+ this.props.project.key,
+ group.name,
+ permission
+ );
+ }
+ }
+
+ handleSelectPermission (permission) {
+ this.props.onSelectPermission(this.props.project.key, permission);
+ }
+
+ render () {
+ const permissions = PERMISSIONS_ORDER.map(p => ({
+ key: p,
+ name: translate('projects_role', p),
+ description: translate('projects_role', p, 'desc')
+ }));
+
+ return (
+ <HoldersList
+ permissions={permissions}
+ selectedPermission={this.props.selectedPermission}
+ users={this.props.users}
+ groups={this.props.groups}
+ onSelectPermission={this.handleSelectPermission.bind(this)}
+ onToggleUser={this.handleToggleUser.bind(this)}
+ onToggleGroup={this.handleToggleGroup.bind(this)}>
+
+ <SearchForm
+ query={this.props.query}
+ filter={this.props.filter}
+ onSearch={this.handleSearch.bind(this)}
+ onFilter={this.handleFilter.bind(this)}/>
+
+ </HoldersList>
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ users: getUsers(state),
+ groups: getGroups(state),
+ query: getQuery(state),
+ filter: getFilter(state),
+ selectedPermission: getSelectedPermission(state)
+});
+
+const mapDispatchToProps = dispatch => ({
+ loadHolders: projectKey => dispatch(loadHolders(projectKey)),
+ onSearch: (projectKey, query) => dispatch(updateQuery(projectKey, query)),
+ onFilter: (projectKey, filter) => dispatch(updateFilter(projectKey, filter)),
+ onSelectPermission: (projectKey, permission) =>
+ dispatch(selectPermission(projectKey, permission)),
+ grantPermissionToUser: (projectKey, login, permission) =>
+ dispatch(grantToUser(projectKey, login, permission)),
+ revokePermissionFromUser: (projectKey, login, permission) =>
+ dispatch(revokeFromUser(projectKey, login, permission)),
+ grantPermissionToGroup: (projectKey, groupName, permission) =>
+ dispatch(grantToGroup(projectKey, groupName, permission)),
+ revokePermissionFromGroup: (projectKey, groupName, permission) =>
+ dispatch(revokeFromGroup(projectKey, groupName, permission))
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(AllHoldersList);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import PageHeader from './PageHeader';
+import AllHoldersList from './AllHoldersList';
+import PageError from '../../shared/components/PageError';
+import '../../styles.css';
+
+// TODO helmet
+
+export default class App extends React.Component {
+ static propTypes = {
+ project: React.PropTypes.object.isRequired
+ };
+
+ render () {
+ return (
+ <div className="page page-limited">
+ <PageHeader project={this.props.project}/>
+ <PageError/>
+ <AllHoldersList project={this.props.project}/>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { connect } from 'react-redux';
+import { translate } from '../../../../helpers/l10n';
+import ApplyTemplateView from '../views/ApplyTemplateView';
+import { loadHolders } from '../store/actions';
+import { isLoading } from '../../shared/store/rootReducer';
+
+class PageHeader extends React.Component {
+ static propTypes = {
+ project: React.PropTypes.object.isRequired,
+ loadHolders: React.PropTypes.func.isRequired,
+ loading: React.PropTypes.bool
+ };
+
+ static defaultProps = {
+ loading: false
+ };
+
+ componentWillMount () {
+ this.handleApplyTemplate = this.handleApplyTemplate.bind(this);
+ }
+
+ handleApplyTemplate (e) {
+ e.preventDefault();
+ e.target.blur();
+ const { project, loadHolders } = this.props;
+ new ApplyTemplateView({ project })
+ .on('done', () => loadHolders(project.key))
+ .render();
+ }
+
+ render () {
+ return (
+ <header className="page-header">
+ <h1 className="page-title">
+ {translate('permissions.page')}
+ </h1>
+
+ {this.props.loading && (
+ <i className="spinner"/>
+ )}
+
+ <div className="page-actions">
+ <button
+ className="js-apply-template"
+ onClick={this.handleApplyTemplate}>
+ Apply Template
+ </button>
+ </div>
+
+ <div className="page-description">
+ {translate('roles.page.description2')}
+ </div>
+ </header>
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ loading: isLoading(state)
+});
+
+const mapDispatchToProps = dispatch => ({
+ loadHolders: projectKey => dispatch(loadHolders(projectKey))
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(PageHeader);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as api from '../../../../api/permissions';
+import { parseError } from '../../../code/utils';
+import { raiseError } from '../../shared/store/actions';
+import {
+ getQuery,
+ getFilter,
+ getSelectedPermission
+} from '../../shared/store/rootReducer';
+
+export const loadHolders = projectKey => (dispatch, getState) => {
+ const query = getQuery(getState());
+ const filter = getFilter(getState());
+ const selectedPermission = getSelectedPermission(getState());
+
+ dispatch({ type: 'REQUEST_HOLDERS', query });
+
+ const requests = [];
+
+ if (filter !== 'groups') {
+ requests.push(api.getPermissionsUsersForComponent(projectKey, query,
+ selectedPermission));
+ } else {
+ requests.push(Promise.resolve([]));
+ }
+
+ if (filter !== 'users') {
+ requests.push(api.getPermissionsGroupsForComponent(projectKey, query,
+ selectedPermission));
+ } else {
+ requests.push(Promise.resolve([]));
+ }
+
+ return Promise.all(requests).then(responses => (
+ dispatch({
+ type: 'RECEIVE_HOLDERS_SUCCESS',
+ users: responses[0],
+ groups: responses[1],
+ query
+ })
+ )).catch(e => {
+ return parseError(e).then(message => dispatch(raiseError(message)));
+ });
+};
+
+export const updateQuery = (projectKey, query = '') => dispatch => {
+ dispatch({ type: 'UPDATE_QUERY', query });
+ if (query.length === 0 || query.length > 2) {
+ dispatch(loadHolders(projectKey));
+ }
+};
+
+export const updateFilter = (projectKey, filter) => dispatch => {
+ dispatch({ type: 'UPDATE_FILTER', filter });
+ dispatch(loadHolders(projectKey));
+};
+
+export const selectPermission = (projectKey, permission) => (dispatch, getState) => {
+ const selectedPermission = getSelectedPermission(getState());
+ if (selectedPermission !== permission) {
+ dispatch({ type: 'SELECT_PERMISSION', permission });
+ } else {
+ dispatch({ type: 'SELECT_PERMISSION', permission: null });
+ }
+ dispatch(loadHolders(projectKey));
+};
+
+export const grantToUser = (projectKey, login, permission) => dispatch => {
+ api.grantPermissionToUser(projectKey, login, permission).then(() => {
+ dispatch({ type: 'GRANT_PERMISSION_TO_USER', login, permission });
+ }).catch(e => {
+ return parseError(e).then(message => dispatch(raiseError(message)));
+ });
+};
+
+export const revokeFromUser = (projectKey, login, permission) => dispatch => {
+ api.revokePermissionFromUser(projectKey, login, permission).then(() => {
+ dispatch({ type: 'REVOKE_PERMISSION_TO_USER', login, permission });
+ }).catch(e => {
+ return parseError(e).then(message => dispatch(raiseError(message)));
+ });
+};
+
+export const grantToGroup = (projectKey, groupName, permission) => dispatch => {
+ api.grantPermissionToGroup(projectKey, groupName, permission).then(() => {
+ dispatch({
+ type: 'GRANT_PERMISSION_TO_GROUP',
+ groupName,
+ permission
+ });
+ }).catch(e => {
+ return parseError(e).then(message => dispatch(raiseError(message)));
+ });
+};
+
+export const revokeFromGroup = (projectKey, groupName, permission) => dispatch => {
+ api.revokePermissionFromGroup(projectKey, groupName, permission).then(() => {
+ dispatch({
+ type: 'REVOKE_PERMISSION_FROM_GROUP',
+ groupName,
+ permission
+ });
+ }).catch(e => {
+ return parseError(e).then(message => dispatch(raiseError(message)));
+ });
+};
--- /dev/null
+<form id="project-permissions-apply-template-form" autocomplete="off">
+ <div class="modal-head">
+ <h2>Apply Permission Template to "{{project.name}}"</h2>
+ </div>
+
+ <div class="modal-body">
+ <div class="js-modal-messages"></div>
+ {{#notNull permissionTemplates}}
+ <div class="modal-field">
+ <label for="project-permissions-template">
+ Template<em class="mandatory">*</em>
+ </label>
+ <select id="project-permissions-template">
+ {{#each permissionTemplates}}
+ <option value="{{id}}">{{name}}</option>
+ {{/each}}
+ </select>
+ </div>
+ {{else}}
+ <i class="spinner"></i>
+ {{/notNull}}
+ </div>
+
+ <div class="modal-foot">
+ {{#notNull permissionTemplates}}
+ <button id="project-permissions-apply-template">Apply</button>
+ {{/notNull}}
+ <a href="#" class="js-modal-close">Cancel</a>
+ </div>
+</form>
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import ModalForm from '../../../../components/common/modal-form';
+import {
+ applyTemplateToProject,
+ getPermissionTemplates
+} from '../../../../api/permissions';
+import Template from '../templates/ApplyTemplateTemplate.hbs';
+
+export default ModalForm.extend({
+ template: Template,
+
+ initialize () {
+ this.loadPermissionTemplates();
+ },
+
+ loadPermissionTemplates () {
+ return getPermissionTemplates().then(r => {
+ this.permissionTemplates = r.permissionTemplates;
+ this.render();
+ });
+ },
+
+ onRender () {
+ ModalForm.prototype.onRender.apply(this, arguments);
+ this.$('#project-permissions-template').select2({
+ width: '250px',
+ minimumResultsForSearch: 20
+ });
+ },
+
+ onFormSubmit () {
+ ModalForm.prototype.onFormSubmit.apply(this, arguments);
+ const permissionTemplate = this.$('#project-permissions-template').val();
+ this.disableForm();
+
+ applyTemplateToProject({
+ projectKey: this.options.project.key,
+ templateId: permissionTemplate
+ }).then(() => {
+ this.trigger('done');
+ this.destroy();
+ }).catch(function (e) {
+ e.response.json().then(r => {
+ this.showErrors(r.errors, r.warnings);
+ this.enableForm();
+ });
+ });
+ },
+
+ serializeData () {
+ return {
+ permissionTemplates: this.permissionTemplates,
+ project: this.options.project
+ };
+ }
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import shallowCompare from 'react-addons-shallow-compare';
+import GroupIcon from './GroupIcon';
+
+export default class GroupHolder extends React.Component {
+ static propTypes = {
+ group: React.PropTypes.object.isRequired,
+ permissions: React.PropTypes.array.isRequired,
+ selectedPermission: React.PropTypes.string,
+ permissionsOrder: React.PropTypes.array.isRequired,
+ onToggle: React.PropTypes.func.isRequired
+ };
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ handleClick (permission, e) {
+ e.preventDefault();
+ e.target.blur();
+ this.props.onToggle(this.props.group, permission);
+ }
+
+ render () {
+ const { selectedPermission } = this.props;
+ const permissionCells = this.props.permissionsOrder.map(p => (
+ <td key={p.key}
+ className="text-center text-middle"
+ style={{ backgroundColor: p.key === selectedPermission ? '#d9edf7' : 'transparent' }}>
+ <button
+ className="button-clean"
+ onClick={this.handleClick.bind(this, p.key)}>
+ {this.props.permissions.includes(p.key) ? (
+ <i className="icon-checkbox icon-checkbox-checked"/>
+ ) : (
+ <i className="icon-checkbox"/>
+ )}
+ </button>
+ </td>
+ ));
+
+ const { group } = this.props;
+
+ return (
+ <tr>
+ <td className="nowrap">
+ <div className="display-inline-block text-middle big-spacer-right">
+ <GroupIcon/>
+ </div>
+ <div className="display-inline-block text-middle">
+ <div>
+ <strong>{group.name}</strong>
+ </div>
+ <div className="little-spacer-top"
+ style={{ whiteSpace: 'normal' }}>
+ {group.description}
+ </div>
+ </div>
+ </td>
+ {permissionCells}
+ </tr>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+
+const GroupIcon = () => {
+ /* eslint max-len: 0 */
+ return (
+ <div style={{ padding: '4px 3px 0' }}>
+ <svg xmlns="http://www.w3.org/2000/svg"
+ width="30"
+ height="28"
+ viewBox="0 0 480 448">
+ <path
+ fill="#aaa"
+ d="M148.25 224q-40.5 1.25-66.25 32H48.5Q28 256 14 245.875T0 216.25Q0 128 31 128q1.5 0 10.875 5.25t24.375 10.625T96 149.25q16.75 0 33.25-5.75Q128 152.75 128 160q0 34.75 20.25 64zM416 383.25q0 30-18.25 47.375T349.25 448h-218.5q-30.25 0-48.5-17.375T64 383.25q0-13.25.875-25.875t3.5-27.25T75 303t10.75-24.375 15.5-20.25T122.625 245t27.875-5q2.5 0 10.75 5.375t18.25 12 26.75 12T240 274.75t33.75-5.375 26.75-12 18.25-12T329.5 240q15.25 0 27.875 5t21.375 13.375 15.5 20.25T405 303t6.625 27.125 3.5 27.25.875 25.875zM160 64q0 26.5-18.75 45.25T96 128t-45.25-18.75T32 64t18.75-45.25T96 0t45.25 18.75T160 64zm176 96q0 39.75-28.125 67.875T240 256t-67.875-28.125T144 160t28.125-67.875T240 64t67.875 28.125T336 160zm144 56.25q0 19.5-14 29.625T431.5 256H398q-25.75-30.75-66.25-32Q352 194.75 352 160q0-7.25-1.25-16.5 16.5 5.75 33.25 5.75 14.75 0 29.75-5.375t24.375-10.625T449 128q31 0 31 88.25zM448 64q0 26.5-18.75 45.25T384 128t-45.25-18.75T320 64t18.75-45.25T384 0t45.25 18.75T448 64z"/>
+ </svg>
+ </div>
+ );
+};
+
+export default GroupIcon;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import UserHolder from './UserHolder';
+import GroupHolder from './GroupHolder';
+
+export default class HoldersList extends React.Component {
+ static propTypes = {
+ permissions: React.PropTypes.array.isRequired,
+ users: React.PropTypes.array.isRequired,
+ groups: React.PropTypes.array.isRequired,
+ selectedPermission: React.PropTypes.string,
+ onSelectPermission: React.PropTypes.func.isRequired,
+ onToggleUser: React.PropTypes.func.isRequired,
+ onToggleGroup: React.PropTypes.func.isRequired
+ };
+
+ handlePermissionClick (permission, e) {
+ e.preventDefault();
+ e.target.blur();
+ this.props.onSelectPermission(permission);
+ }
+
+ renderTableHeader () {
+ const { selectedPermission } = this.props;
+ const cells = this.props.permissions.map(p => (
+ <th key={p.key}
+ className="permission-column text-center"
+ style={{ backgroundColor: p.key === selectedPermission ? '#d9edf7' : 'transparent' }}>
+ <a href="#" onClick={this.handlePermissionClick.bind(this, p.key)}>
+ {p.name}
+ </a>
+ <i className="icon-help little-spacer-left"
+ title={p.description}
+ data-toggle="tooltip"/>
+ </th>
+ ));
+ return (
+ <thead>
+ <tr>
+ <td className="bordered-bottom">
+ {this.props.children}
+ </td>
+ {cells}
+ </tr>
+ </thead>
+ );
+ }
+
+ render () {
+ const users = this.props.users.map(user => (
+ <UserHolder
+ key={'user-' + user.login}
+ user={user}
+ permissions={user.permissions}
+ selectedPermission={this.props.selectedPermission}
+ permissionsOrder={this.props.permissions}
+ onToggle={this.props.onToggleUser}/>
+ ));
+
+ const groups = this.props.groups.map(group => (
+ <GroupHolder
+ key={'group-' + group.id}
+ group={group}
+ permissions={group.permissions}
+ selectedPermission={this.props.selectedPermission}
+ permissionsOrder={this.props.permissions}
+ onToggle={this.props.onToggleGroup}/>
+ ));
+
+ return (
+ <table className="data zebra permissions-table">
+ {this.renderTableHeader()}
+ <tbody>
+ {users}
+ {groups}
+ </tbody>
+ </table>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { connect } from 'react-redux';
+import { getError } from '../store/rootReducer';
+
+class PageError extends React.Component {
+ static propTypes = {
+ message: React.PropTypes.string
+ };
+
+ render () {
+ const { message } = this.props;
+
+ if (!message) {
+ return null;
+ }
+
+ return (
+ <div className="alert alert-danger">
+ {message}
+ </div>
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ message: getError(state)
+});
+
+export default connect(
+ mapStateToProps
+)(PageError);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import RadioToggle from '../../../../components/controls/RadioToggle';
+import { translate, translateWithParameters } from '../../../../helpers/l10n';
+
+export default class SearchForm extends React.Component {
+ static propTypes = {
+ query: React.PropTypes.string,
+ filter: React.PropTypes.oneOf(['all', 'users', 'groups']),
+ onSearch: React.PropTypes.func,
+ onFilter: React.PropTypes.func
+ };
+
+ componentWillMount () {
+ this.handleSubmit = this.handleSubmit.bind(this);
+ this.handleSearch = this.handleSearch.bind(this);
+ }
+
+ handleSubmit (e) {
+ e.preventDefault();
+ this.handleSearch();
+ }
+
+ handleSearch () {
+ const { value } = this.refs.searchInput;
+ this.props.onSearch(value);
+ }
+
+ handleFilter (filter) {
+ this.props.onFilter(filter);
+ }
+
+ render () {
+ const { query, filter } = this.props;
+
+ const filterOptions = [
+ { value: 'all', label: 'All' },
+ { value: 'users', label: 'Users' },
+ { value: 'groups', label: 'Groups' }
+ ];
+
+ return (
+ <div>
+
+ <RadioToggle
+ value={filter}
+ options={filterOptions}
+ name="users-or-groups"
+ onCheck={this.handleFilter.bind(this)}/>
+
+ <form
+ className="search-box display-inline-block text-middle big-spacer-left"
+ onSubmit={this.handleSubmit}>
+ <button className="search-box-submit button-clean">
+ <i className="icon-search"></i>
+ </button>
+ <input
+ ref="searchInput"
+ value={query}
+ className="search-box-input"
+ style={{ width: 100 }}
+ type="search"
+ placeholder={translate('search_verb')}
+ onChange={this.handleSearch.bind(this)}/>
+ {query.length > 0 && query.length < 3 && (
+ <div className="search-box-input-note tooltip bottom fade in">
+ <div className="tooltip-inner">
+ {translateWithParameters('select2.tooShort', 3)}
+ </div>
+ <div className="tooltip-arrow" style={{ left: 23 }}/>
+ </div>
+ )}
+ </form>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import shallowCompare from 'react-addons-shallow-compare';
+import Avatar from '../../../../components/ui/Avatar';
+
+export default class UserHolder extends React.Component {
+ static propTypes = {
+ user: React.PropTypes.object.isRequired,
+ permissions: React.PropTypes.array.isRequired,
+ selectedPermission: React.PropTypes.string,
+ permissionsOrder: React.PropTypes.array.isRequired,
+ onToggle: React.PropTypes.func.isRequired
+ };
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ handleClick (permission, e) {
+ e.preventDefault();
+ e.target.blur();
+ this.props.onToggle(this.props.user, permission);
+ }
+
+ render () {
+ const { selectedPermission } = this.props;
+ const permissionCells = this.props.permissionsOrder.map(p => (
+ <td key={p.key}
+ className="text-center text-middle"
+ style={{ backgroundColor: p.key === selectedPermission ? '#d9edf7' : 'transparent' }}>
+ <button
+ className="button-clean"
+ onClick={this.handleClick.bind(this, p.key)}>
+ {this.props.permissions.includes(p.key) ? (
+ <i className="icon-checkbox icon-checkbox-checked"/>
+ ) : (
+ <i className="icon-checkbox"/>
+ )}
+ </button>
+ </td>
+ ));
+
+ const { user } = this.props;
+
+ return (
+ <tr>
+ <td className="nowrap">
+ <Avatar
+ email={user.email}
+ size={36}
+ className="text-middle big-spacer-right"/>
+ <div className="display-inline-block text-middle">
+ <div>
+ <strong>{user.name}</strong>
+ <span className="note spacer-left">{user.login}</span>
+ </div>
+ <div className="little-spacer-top">{user.email}</div>
+ </div>
+ </td>
+ {permissionCells}
+ </tr>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+export const raiseError = message => ({
+ type: 'ERROR',
+ message
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+const error = (state = null, action = {}) => {
+ switch (action.type) {
+ case 'RECEIVE_HOLDERS_SUCCESS':
+ case 'GRANT_PERMISSION_TO_USER':
+ case 'REVOKE_PERMISSION_TO_USER':
+ case 'GRANT_PERMISSION_TO_GROUP':
+ case 'REVOKE_PERMISSION_FROM_GROUP':
+ return null;
+ case 'ERROR':
+ return action.message;
+ default:
+ return state;
+ }
+};
+
+export default error;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+const filter = (state = 'all', action = {}) => {
+ switch (action.type) {
+ case 'UPDATE_FILTER':
+ return action.filter;
+ default:
+ return state;
+ }
+};
+
+export default filter;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import keyBy from 'lodash/keyBy';
+
+const byName = (state = {}, action = {}) => {
+ if (action.type === 'RECEIVE_HOLDERS_SUCCESS') {
+ const newGroups = keyBy(action.groups, 'name');
+ return { ...state, ...newGroups };
+ } else if (action.type === 'GRANT_PERMISSION_TO_GROUP') {
+ const newGroup = { ...state[action.groupName] };
+ newGroup.permissions = [...newGroup.permissions, action.permission];
+ return { ...state, [action.groupName]: newGroup };
+ } else if (action.type === 'REVOKE_PERMISSION_FROM_GROUP') {
+ const newGroup = { ...state[action.groupName] };
+ newGroup.permissions = newGroup.permissions
+ .filter(p => p !== action.permission);
+ return { ...state, [action.groupName]: newGroup };
+ } else {
+ return state;
+ }
+};
+
+export default byName;
+
+export const getGroups = state => state;
+
+export const getGroupByName = (state, name) => state[name];
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { combineReducers } from 'redux';
+import byName, { getGroupByName } from './byName';
+import names, { getNames } from './names';
+
+export default combineReducers({
+ byName,
+ names
+});
+
+export const getGroups = state =>
+ getNames(state.names).map(name => getGroupByName(state.byName, name));
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+const names = (state = [], action = {}) => {
+ if (action.type === 'RECEIVE_HOLDERS_SUCCESS') {
+ return action.groups.map(group => group.name);
+ } else {
+ return state;
+ }
+};
+
+export default names;
+
+export const getNames = state => state;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+const loading = (state = false, action = {}) => {
+ switch (action.type) {
+ case 'REQUEST_HOLDERS':
+ return true;
+ case 'RECEIVE_HOLDERS_SUCCESS':
+ return false;
+ default:
+ return state;
+ }
+};
+
+export default loading;
+
+export const isLoading = state => state;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+const query = (state = '', action = {}) => {
+ switch (action.type) {
+ case 'UPDATE_QUERY':
+ case 'REQUEST_HOLDERS':
+ return action.query;
+ default:
+ return state;
+ }
+};
+
+export default query;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { combineReducers } from 'redux';
+import users, * as fromUsers from './users/users';
+import groups, * as fromGroups from './groups/groups';
+import loading, * as fromLoading from './loading';
+import query from './query';
+import filter from './filter';
+import selectedPermission from './selectedPermission';
+import error from './error';
+
+export default combineReducers({
+ users,
+ groups,
+ loading,
+ query,
+ filter,
+ selectedPermission,
+ error
+});
+
+export const getUsers = state => fromUsers.getUsers(state.users);
+
+export const getGroups = state => fromGroups.getGroups(state.groups);
+
+export const isLoading = state => fromLoading.isLoading(state.loading);
+
+export const getQuery = state => state.query;
+
+export const getFilter = state => state.filter;
+
+export const getSelectedPermission = state => state.selectedPermission;
+
+export const getError = state => state.error;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+const selectedPermission = (state = null, action = {}) => {
+ switch (action.type) {
+ case 'SELECT_PERMISSION':
+ return action.permission;
+ default:
+ return state;
+ }
+};
+
+export default selectedPermission;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import keyBy from 'lodash/keyBy';
+
+const byLogin = (state = {}, action = {}) => {
+ if (action.type === 'RECEIVE_HOLDERS_SUCCESS') {
+ const newUsers = keyBy(action.users, 'login');
+ return { ...state, ...newUsers };
+ } else if (action.type === 'GRANT_PERMISSION_TO_USER') {
+ const newUser = { ...state[action.login] };
+ newUser.permissions = [...newUser.permissions, action.permission];
+ return { ...state, [action.login]: newUser };
+ } else if (action.type === 'REVOKE_PERMISSION_TO_USER') {
+ const newUser = { ...state[action.login] };
+ newUser.permissions = newUser.permissions
+ .filter(p => p !== action.permission);
+ return { ...state, [action.login]: newUser };
+ } else {
+ return state;
+ }
+};
+
+export default byLogin;
+
+export const getUsers = state => state;
+
+export const getUserByLogin = (state, login) => state[login];
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+const logins = (state = [], action = {}) => {
+ if (action.type === 'RECEIVE_HOLDERS_SUCCESS') {
+ return action.users.map(user => user.login);
+ } else {
+ return state;
+ }
+};
+
+export default logins;
+
+export const getLogins = state => state;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { combineReducers } from 'redux';
+import byLogin, { getUserByLogin } from './byLogin';
+import logins, { getLogins } from './logins';
+
+export default combineReducers({
+ byLogin,
+ logins
+});
+
+export const getUsers = state =>
+ getLogins(state.logins).map(login => getUserByLogin(state.byLogin, login));
--- /dev/null
+.permissions-table {
+ table-layout: fixed;
+}
+
+.permissions-table > tbody > tr > td {
+ border-bottom: 10px solid #fff !important;
+}
+
+.permissions-table .permission-column {
+ width: 12%;
+ min-width: 112px;
+}
+
+.permissions-table .actions-column {
+ width: 130px;
+ text-align: right;
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import React from 'react';
-import ReactDOM from 'react-dom';
-import Main from './main';
-
-function requestPermissionTemplates () {
- return $.get(window.baseUrl + '/api/permissions/search_templates');
-}
-
-window.sonarqube.appStarted.then(options => {
- requestPermissionTemplates().done(r => {
- const el = document.querySelector(options.el);
- ReactDOM.render(<Main permissionTemplates={r.permissionTemplates}
- componentId={window.sonarqube.componentId}
- rootQualifiers={options.rootQualifiers}/>, el);
- });
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import _ from 'underscore';
-import ModalForm from '../../components/common/modal-form';
-import { applyTemplateToProject, bulkApplyTemplateToProject } from '../../api/permissions';
-import Template from './templates/project-permissions-apply-template.hbs';
-
-export default ModalForm.extend({
- template: Template,
-
- onRender () {
- ModalForm.prototype.onRender.apply(this, arguments);
- this.$('#project-permissions-template').select2({
- width: '250px',
- minimumResultsForSearch: 20
- });
- },
-
- onFormSubmit () {
- ModalForm.prototype.onFormSubmit.apply(this, arguments);
- const that = this;
- const permissionTemplate = this.$('#project-permissions-template').val();
- this.disableForm();
-
- if (this.options.project) {
- applyTemplateToProject({
- data: { projectId: this.options.project.id, templateId: permissionTemplate }
- }).done(function () {
- that.options.refresh();
- that.destroy();
- }).fail(function (jqXHR) {
- that.enableForm();
- that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
- });
- } else {
- const data = { templateId: permissionTemplate };
- if (this.options.query) {
- data.q = this.options.query;
- }
- if (this.options.filter && this.options.filter !== '__ALL__') {
- data.qualifier = this.options.filter;
- }
-
- bulkApplyTemplateToProject({ data }).done(function () {
- that.options.refresh();
- that.destroy();
- }).fail(function (jqXHR) {
- that.enableForm();
- that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
- });
- }
- },
-
- serializeData () {
- return _.extend(ModalForm.prototype.serializeData.apply(this, arguments), {
- permissionTemplates: this.options.permissionTemplates,
- project: this.options.project,
- projectsCount: _.size(this.options.projects)
- });
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import _ from 'underscore';
-import Modal from '../../components/common/modals';
-import '../../components/SelectList';
-import Template from './templates/project-permissions-groups.hbs';
-
-function getSearchUrl (permission, project) {
- return `${window.baseUrl}/api/permissions/groups?ps=100&permission=${permission}&projectId=${project}`;
-}
-
-export default Modal.extend({
- template: Template,
-
- onRender () {
- Modal.prototype.onRender.apply(this, arguments);
- new window.SelectList({
- el: this.$('#project-permissions-groups'),
- width: '100%',
- readOnly: false,
- focusSearch: false,
- format (item) {
- return item.name;
- },
- queryParam: 'q',
- searchUrl: getSearchUrl(this.options.permission, this.options.project),
- selectUrl: window.baseUrl + '/api/permissions/add_group',
- deselectUrl: window.baseUrl + '/api/permissions/remove_group',
- extra: {
- permission: this.options.permission,
- projectId: this.options.project
- },
- selectParameter: 'groupName',
- selectParameterValue: 'name',
- parse (r) {
- this.more = false;
- return r.groups;
- }
- });
- },
-
- onDestroy () {
- if (this.options.refresh) {
- this.options.refresh();
- }
- Modal.prototype.onDestroy.apply(this, arguments);
- },
-
- serializeData () {
- return _.extend(Modal.prototype.serializeData.apply(this, arguments), {
- projectName: this.options.projectName
- });
- }
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import _ from 'underscore';
-import React from 'react';
-
-import Permissions from './permissions';
-import PermissionsFooter from './permissions-footer';
-import Search from './search';
-import ApplyTemplateView from './apply-template-view';
-import { translate } from '../../helpers/l10n';
-import { TooltipsContainer } from '../../components/mixins/tooltips-mixin';
-import '../permission-templates/styles.css';
-
-const PERMISSIONS_ORDER = ['user', 'codeviewer', 'issueadmin', 'admin', 'scan'];
-
-export default React.createClass({
- propTypes: {
- permissionTemplates: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
- },
-
- getInitialState() {
- return { ready: false, permissions: [], projects: [], total: 0, filter: '__ALL__' };
- },
-
- componentDidMount() {
- this.requestPermissions();
- },
-
- sortPermissions(permissions) {
- return _.sortBy(permissions, p => PERMISSIONS_ORDER.indexOf(p.key));
- },
-
- 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
- const permissions = basePermissions.map(basePermission => {
- const projectPermission = _.findWhere(project.permissions, { key: basePermission.key });
- return _.extend({ usersCount: 0, groupsCount: 0 }, basePermission, projectPermission);
- });
- return _.extend({}, project, { permissions });
- });
- },
-
- requestPermissions(page = 1, query = '', filter = this.state.filter) {
- const url = window.baseUrl + '/api/permissions/search_project_permissions';
- let data = { p: page, q: query };
- if (filter !== '__ALL__') {
- data.qualifier = filter;
- }
- if (this.props.componentId) {
- data = { projectId: this.props.componentId };
- }
- this.setState({ ready: false }, () => {
- $.get(url, data).done(r => {
- const permissions = this.sortPermissions(r.permissions);
- let projects = this.mergePermissionsToProjects(r.projects, permissions);
- if (page > 1) {
- projects = [].concat(this.state.projects, projects);
- }
- this.setState({
- ready: true,
- projects,
- permissions,
- total: r.paging.total,
- page: r.paging.pageIndex,
- query,
- filter
- });
- });
- });
- },
-
- loadMore() {
- this.requestPermissions(this.state.page + 1, this.state.query);
- },
-
- search(query) {
- this.requestPermissions(1, query);
- },
-
- handleFilter(filter) {
- this.requestPermissions(1, this.state.query, filter);
- },
-
- refresh() {
- this.requestPermissions(1, this.state.query);
- },
-
- bulkApplyTemplate(e) {
- e.preventDefault();
- new ApplyTemplateView({
- query: this.state.query,
- filter: this.state.filter,
- permissionTemplates: this.props.permissionTemplates,
- refresh: () => this.requestPermissions(1, this.state.query, this.state.filter)
- }).render();
- },
-
- renderBulkApplyButton() {
- if (this.props.componentId) {
- return null;
- }
- return (
- <button onClick={this.bulkApplyTemplate} className="js-bulk-apply-template">Bulk Apply Template</button>
- );
- },
-
- renderSpinner () {
- if (this.state.ready) {
- return null;
- }
- return <i className="spinner"/>;
- },
-
- render() {
- return (
- <TooltipsContainer>
- <div className="page">
- <header id="project-permissions-header" className="page-header">
- <h1 className="page-title">{translate('roles.page')}</h1>
- {this.renderSpinner()}
- <div className="page-actions">
- {this.renderBulkApplyButton()}
- </div>
- <p className="page-description">
- {translate('roles.page.description2')}
- </p>
- </header>
-
- <Search {...this.props}
- filter={this.state.filter}
- search={this.search}
- onFilter={this.handleFilter}/>
-
- <Permissions
- ready={this.state.ready}
- projects={this.state.projects}
- permissions={this.state.permissions}
- permissionTemplates={this.props.permissionTemplates}
- refresh={this.refresh}/>
-
- <PermissionsFooter {...this.props}
- ready={this.state.ready}
- count={this.state.projects.length}
- total={this.state.total}
- loadMore={this.loadMore}/>
- </div>
- </TooltipsContainer>
- );
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import classNames from 'classnames';
-import React from 'react';
-
-export default React.createClass({
- propTypes: {
- count: React.PropTypes.number.isRequired,
- total: React.PropTypes.number.isRequired,
- loadMore: React.PropTypes.func.isRequired
- },
-
- handleLoadMore (e) {
- e.preventDefault();
- this.props.loadMore();
- },
-
- render() {
- if (this.props.componentId) {
- return null;
- }
- const hasMore = this.props.total > this.props.count;
- const loadMoreLink = <a onClick={this.handleLoadMore} className="spacer-left" href="#">show more</a>;
- const className = classNames('spacer-top note text-center', { 'new-loading': !this.props.ready });
- return (
- <footer className={className}>
- {this.props.count}/{this.props.total} shown
- {hasMore ? loadMoreLink : null}
- </footer>
- );
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-
-export default React.createClass({
- propTypes: {
- permissions: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
- },
-
- render() {
- const cells = this.props.permissions.map(p => (
- <th key={p.key} className="permission-column">
- {p.name}
- <i
- className="icon-help little-spacer-left"
- title={p.description}
- data-toggle="tooltip"/>
- </th>
- ));
-
- return (
- <thead>
- <tr>
- <th> </th>
- {cells}
- <th className="actions-column"> </th>
- </tr>
- </thead>
- );
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import classNames from 'classnames';
-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,
- permissionTemplates: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
- refresh: React.PropTypes.func.isRequired
- },
-
- render() {
- const projects = this.props.projects.map(p => {
- return <Project
- key={p.id}
- project={p}
- permissionTemplates={this.props.permissionTemplates}
- refresh={this.props.refresh}/>;
- });
- const className = classNames(
- 'data zebra permissions-table',
- { 'new-loading': !this.props.ready });
- return (
- <table id="projects" className={className}>
- <PermissionsHeader permissions={this.props.permissions}/>
- <tbody>{projects}</tbody>
- </table>
- );
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import UsersView from './users-view';
-import GroupsView from './groups-view';
-import ApplyTemplateView from './apply-template-view';
-import { getComponentUrl } from '../../helpers/urls';
-import QualifierIcon from '../../components/shared/qualifier-icon';
-
-export default React.createClass({
- propTypes: {
- project: React.PropTypes.object.isRequired,
- permissionTemplates: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
- refresh: React.PropTypes.func.isRequired
- },
-
- showGroups(permission, e) {
- e.preventDefault();
- new GroupsView({
- permission,
- project: this.props.project.id,
- projectName: this.props.project.name,
- refresh: this.props.refresh
- }).render();
- },
-
- showUsers(permission, e) {
- e.preventDefault();
- new UsersView({
- permission,
- project: this.props.project.id,
- projectName: this.props.project.name,
- refresh: this.props.refresh
- }).render();
- },
-
- applyTemplate(e) {
- e.preventDefault();
- new ApplyTemplateView({
- permissionTemplates: this.props.permissionTemplates,
- project: this.props.project,
- refresh: this.props.refresh
- }).render();
- },
-
- render() {
- const permissions = this.props.project.permissions.map(p => {
- return (
- <td key={p.key}>
- <table>
- <tbody>
- <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>
- </tbody>
- </table>
- </td>
- );
- });
- return (
- <tr>
- <td>
- <span className="little-spacer-right">
- <QualifierIcon qualifier={this.props.project.qualifier}/>
- </span>
- <a href={getComponentUrl(this.props.project.key)}>{this.props.project.name}</a>
- </td>
- {permissions}
- <td className="thin nowrap text-right">
- <button onClick={this.applyTemplate} className="js-apply-template">Apply Template</button>
- </td>
- </tr>
- );
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import { translate } from '../../helpers/l10n';
-
-import RadioToggle from '../../components/controls/RadioToggle';
-
-const rootQualifiersToOptions = (qualifiers) => {
- return qualifiers.map(q => {
- return {
- value: q,
- label: translate('qualifiers', q)
- };
- });
-};
-
-export const QualifierFilter = ({ rootQualifiers, filter, onFilter }) => {
- const options = [{ value: '__ALL__', label: 'All' }, ...rootQualifiersToOptions(rootQualifiers)];
-
- return (
- <div className="display-inline-block text-top nowrap big-spacer-right">
- <RadioToggle value={filter}
- options={options}
- name="qualifier"
- onCheck={onFilter}/>
- </div>
- );
-};
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import _ from 'underscore';
-import React from 'react';
-
-import { QualifierFilter } from './qualifier-filter';
-
-export default React.createClass({
- propTypes: {
- search: React.PropTypes.func.isRequired
- },
-
- componentWillMount () {
- this.search = _.debounce(this.search, 250);
- },
-
- onSubmit(e) {
- e.preventDefault();
- this.search();
- },
-
- search() {
- const q = this.refs.input.value;
- this.props.search(q);
- },
-
- render() {
- if (this.props.componentId) {
- return null;
- }
- return (
- <div className="panel panel-vertical bordered-bottom spacer-bottom">
-
- {this.props.rootQualifiers.length > 1 && <QualifierFilter filter={this.props.filter}
- rootQualifiers={this.props.rootQualifiers}
- onFilter={this.props.onFilter}/>}
-
- <form onSubmit={this.onSubmit} className="search-box display-inline-block text-top">
- <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>
- );
- }
-});
+++ /dev/null
-<form id="project-permissions-apply-template-form" autocomplete="off">
- <div class="modal-head">
- {{#if project}}
- <h2>Apply Permission Template to "{{project.name}}"</h2>
- {{else}}
- <h2>Bulk Apply Permission Template</h2>
- {{/if}}
- </div>
- <div class="modal-body">
- <div class="js-modal-messages"></div>
-
- <div class="modal-field">
- <label for="project-permissions-template">Template<em class="mandatory">*</em></label>
- <select id="project-permissions-template">
- {{#each permissionTemplates}}
- <option value="{{id}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
- </div>
- <div class="modal-foot">
- <button id="project-permissions-apply-template">Apply</button>
- <a href="#" class="js-modal-close">Cancel</a>
- </div>
-</form>
+++ /dev/null
-<div class="modal-head">
- <h2>Update Groups of "{{projectName}}"</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>
+++ /dev/null
-<div class="modal-head">
- <h2>Update Users of "{{projectName}}"</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>
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import _ from 'underscore';
-import Modal from '../../components/common/modals';
-import '../../components/SelectList';
-import Template from './templates/project-permissions-users.hbs';
-
-export default Modal.extend({
- template: Template,
-
- onRender () {
- Modal.prototype.onRender.apply(this, arguments);
- const searchUrl = window.baseUrl + '/api/permissions/users?ps=100&permission=' + this.options.permission +
- '&projectId=' + this.options.project;
- new window.SelectList({
- searchUrl,
- el: this.$('#project-permissions-users'),
- width: '100%',
- readOnly: false,
- focusSearch: false,
- format (item) {
- return `${item.name}<br><span class="note">${item.login}</span>`;
- },
- queryParam: 'q',
- selectUrl: window.baseUrl + '/api/permissions/add_user',
- deselectUrl: window.baseUrl + '/api/permissions/remove_user',
- extra: {
- permission: this.options.permission,
- projectId: this.options.project
- },
- selectParameter: 'login',
- selectParameterValue: 'login',
- parse (r) {
- this.more = false;
- return r.users;
- }
- });
- },
-
- onDestroy () {
- if (this.options.refresh) {
- this.options.refresh();
- }
- Modal.prototype.onDestroy.apply(this, arguments);
- },
-
- serializeData () {
- return _.extend(Modal.prototype.serializeData.apply(this, arguments), {
- projectName: this.options.projectName
- });
- }
-});
-
*/
import React from 'react';
import md5 from 'blueimp-md5';
+import classNames from 'classnames';
export default class Avatar extends React.Component {
static propTypes = {
email: React.PropTypes.string,
- size: React.PropTypes.number.isRequired
+ size: React.PropTypes.number.isRequired,
+ className: React.PropTypes.string
};
render () {
.replace('{EMAIL_MD5}', emailHash)
.replace('{SIZE}', this.props.size * 2);
+ const className = classNames(this.props.className, 'rounded');
+
return (
- <img className="rounded"
+ <img className={className}
src={url}
width={this.props.size}
height={this.props.size}
top: 1px;
}
}
+
+.search-box-input-note {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ line-height: 1;
+ color: #777;
+ font-size: @smallFontSize;
+ white-space: nowrap;
+}
+
.button-clean:focus {
margin: 0;
padding: 0;
+ line-height: 1;
border: none;
background: transparent;
+ box-shadow: none;
color: @baseFontColor;
}
table.data > thead > tr > th {
vertical-align: top;
- line-height: 24px;
+ line-height: 18px;
padding: 8px 10px;
border-bottom: 1px solid @barBorderColor;
font-weight: 500;
<% content_for :extra_script do %>
- <script>
- window.sonarqube.componentId = '<%= @project.uuid -%>';
- </script>
<script src="<%= ApplicationController.root_context -%>/js/bundles/project-permissions.js?v=<%= sonar_version -%>"></script>
<% end %>
'component-measures': './src/main/js/apps/component-measures/app.js',
'custom-measures': './src/main/js/apps/custom-measures/app.js',
'dashboard': './src/main/js/apps/dashboard/app.js',
- 'global-permissions': './src/main/js/apps/global-permissions/app.js',
+ 'global-permissions': './src/main/js/apps/permissions/global/app.js',
'groups': './src/main/js/apps/groups/app.js',
'issues': './src/main/js/apps/issues/app.js',
'maintenance': './src/main/js/apps/maintenance/app.js',
'metrics': './src/main/js/apps/metrics/app.js',
'overview': './src/main/js/apps/overview/app.js',
'permission-templates': './src/main/js/apps/permission-templates/app.js',
- 'project-permissions': './src/main/js/apps/project-permissions/app.js',
+ 'project-permissions': './src/main/js/apps/permissions/project/app.js',
'projects': './src/main/js/apps/projects/app.js',
'quality-gates': './src/main/js/apps/quality-gates/app.js',
'quality-profiles': './src/main/js/apps/quality-profiles/app.js',
{ test: require.resolve('jquery'), loader: 'expose?$!expose?jQuery' },
{ test: require.resolve('underscore'), loader: 'expose?_' },
{ test: require.resolve('backbone'), loader: 'expose?Backbone' },
- { test: require.resolve('backbone.marionette'), loader: 'expose?Marionette' },
+ {
+ test: require.resolve('backbone.marionette'),
+ loader: 'expose?Marionette'
+ },
{ test: require.resolve('d3'), loader: 'expose?d3' },
{ test: require.resolve('react'), loader: 'expose?React' },
{ test: require.resolve('react-dom'), loader: 'expose?ReactDOM' },