</tr>
<tr>
<td>open</td>
- <td>/projects</td>
+ <td>/projects_admin</td>
<td></td>
</tr>
<tr>
</tr>
<tr>
<td>open</td>
- <td>/projects</td>
+ <td>/projects_admin</td>
<td></td>
</tr>
<tr>
</tr>
<tr>
<td>open</td>
- <td>/projects</td>
+ <td>/projects_admin</td>
<td></td>
</tr>
<tr>
'permission-templates': './src/main/js/apps/permission-templates/app.js',
'project-admin': './src/main/js/apps/project-admin/app.js',
'project-permissions': './src/main/js/apps/permissions/project/app.js',
- 'projects': './src/main/js/apps/projects/app.js',
+ 'projects-admin': './src/main/js/apps/projects-admin/app.js',
'quality-gates': './src/main/js/apps/quality-gates/app.js',
'quality-profiles': './src/main/js/apps/quality-profiles/app.js',
'settings': './src/main/js/apps/settings/app.js',
--- /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 { shallow } from 'enzyme';
+import Projects from '../projects';
+import Checkbox from '../../../components/controls/Checkbox';
+
+it('should render list of projects with no selection', () => {
+ const projects = [
+ { id: '1', key: 'a', name: 'A', qualifier: 'TRK' },
+ { id: '2', key: 'b', name: 'B', qualifier: 'TRK' }
+ ];
+
+ const result = shallow(<Projects projects={projects} selection={[]} refresh={jest.fn()}/>);
+ expect(result.find('tr').length).toBe(2);
+ expect(result.find(Checkbox).filterWhere(n => n.prop('checked')).length).toBe(0);
+});
+
+it('should render list of projects with one selected', () => {
+ const projects = [
+ { id: '1', key: 'a', name: 'A', qualifier: 'TRK' },
+ { id: '2', key: 'b', name: 'B', qualifier: 'TRK' }
+ ];
+ const selection = ['1'];
+
+ const result = shallow(<Projects projects={projects} selection={selection} refresh={jest.fn()}/>);
+ expect(result.find('tr').length).toBe(2);
+ expect(result.find(Checkbox).filterWhere(n => n.prop('checked')).length).toBe(1);
+});
--- /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';
+import { getCurrentUser } from '../../api/users';
+
+window.sonarqube.appStarted.then(options => {
+ getCurrentUser().then(user => {
+ const el = document.querySelector(options.el);
+ const hasProvisionPermission = user.permissions.global.indexOf('provisioning') !== -1;
+ const topLevelQualifiers = options.rootQualifiers;
+ ReactDOM.render(<Main hasProvisionPermission={hasProvisionPermission}
+ topLevelQualifiers={topLevelQualifiers}/>, 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.
+ */
+export const PAGE_SIZE = 50;
+
+export const QUALIFIERS_ORDER = ['TRK', 'VW', 'DEV'];
+
+export const TYPE = {
+ ALL: 'ALL',
+ PROVISIONED: 'PROVISIONED',
+ GHOSTS: 'GHOSTS'
+};
--- /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 { createProject } from '../../api/components';
+import Template from './templates/projects-create-form.hbs';
+
+export default ModalForm.extend({
+ template: Template,
+
+ onRender () {
+ ModalForm.prototype.onRender.apply(this, arguments);
+ this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
+ },
+
+ onDestroy () {
+ ModalForm.prototype.onDestroy.apply(this, arguments);
+ this.$('[data-toggle="tooltip"]').tooltip('destroy');
+ },
+
+ onFormSubmit () {
+ ModalForm.prototype.onFormSubmit.apply(this, arguments);
+ this.sendRequest();
+ },
+
+ sendRequest () {
+ const data = {
+ name: this.$('#create-project-name').val(),
+ branch: this.$('#create-project-branch').val(),
+ key: this.$('#create-project-key').val()
+ };
+ this.disableForm();
+ return createProject(data)
+ .then(project => {
+ if (this.options.refresh) {
+ this.options.refresh();
+ }
+ this.enableForm();
+ this.createdProject = project;
+ this.render();
+ })
+ .catch(error => {
+ this.enableForm();
+ if (error.response.status === 400) {
+ error.response.json().then(obj => this.showErrors([{ msg: obj.err_msg }]));
+ }
+ });
+ },
+
+ serializeData () {
+ return {
+ ...ModalForm.prototype.serializeData.apply(this, arguments),
+ createdProject: this.createdProject
+ };
+ }
+});
--- /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 Template from './templates/projects-delete.hbs';
+
+export default ModalForm.extend({
+ template: Template,
+
+ onFormSubmit () {
+ ModalForm.prototype.onFormSubmit.apply(this, arguments);
+ this.options.deleteProjects();
+ this.destroy();
+ }
+});
+
--- /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';
+
+export default ModalForm.extend({
+
+ onRender () {
+ ModalForm.prototype.onRender.apply(this, arguments);
+ this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
+ },
+
+ onDestroy () {
+ ModalForm.prototype.onDestroy.apply(this, arguments);
+ this.$('[data-toggle="tooltip"]').tooltip('destroy');
+ },
+
+ onFormSubmit () {
+ ModalForm.prototype.onFormSubmit.apply(this, arguments);
+ this.sendRequest();
+ }
+
+});
--- /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 CreateView from './create-view';
+import BulkApplyTemplateView from './views/BulkApplyTemplateView';
+
+export default class Header extends React.Component {
+ static propTypes = {
+ hasProvisionPermission: React.PropTypes.bool.isRequired
+ };
+
+ createProject () {
+ new CreateView({
+ refresh: this.props.refresh
+ }).render();
+ }
+
+ bulkApplyTemplate () {
+ new BulkApplyTemplateView({
+ total: this.props.total,
+ selection: this.props.selection,
+ query: this.props.query,
+ qualifier: this.props.qualifier
+ }).render();
+ }
+
+ renderCreateButton () {
+ if (!this.props.hasProvisionPermission) {
+ return null;
+ }
+ return (
+ <li>
+ <button onClick={this.createProject.bind(this)}>
+ Create Project
+ </button>
+ </li>
+ );
+ }
+
+ renderBulkApplyTemplateButton () {
+ return (
+ <li>
+ <button onClick={this.bulkApplyTemplate.bind(this)}>
+ Bulk Apply Permission Template
+ </button>
+ </li>
+ );
+ }
+
+ render () {
+ return (
+ <header className="page-header">
+ <h1 className="page-title">Projects Management</h1>
+ <div className="page-actions">
+ <ul className="list-inline">
+ {this.renderCreateButton()}
+ {this.renderBulkApplyTemplateButton()}
+ </ul>
+ </div>
+ <p className="page-description">Use this page to delete multiple projects at once, or to provision projects
+ if you would like to configure them before the first analysis. Note that once a project is provisioned, you
+ have access to perform all project configurations on it.</p>
+ </header>
+ );
+ }
+}
--- /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 Header from './header';
+import Search from './search';
+import Projects from './projects';
+import { PAGE_SIZE, TYPE } from './constants';
+import {
+ getComponents,
+ getProvisioned,
+ getGhosts,
+ deleteComponents
+} from '../../api/components';
+import ListFooter from '../../components/controls/ListFooter';
+
+export default React.createClass({
+ propTypes: {
+ hasProvisionPermission: React.PropTypes.bool.isRequired,
+ topLevelQualifiers: React.PropTypes.array.isRequired
+ },
+
+ getInitialState() {
+ return {
+ ready: false,
+ projects: [],
+ total: 0,
+ page: 1,
+ query: '',
+ qualifiers: 'TRK',
+ type: TYPE.ALL,
+ selection: []
+ };
+ },
+
+ componentWillMount () {
+ this.requestProjects = _.debounce(this.requestProjects, 250);
+ },
+
+ componentDidMount() {
+ this.requestProjects();
+ },
+
+ getFilters() {
+ const filters = { ps: PAGE_SIZE };
+ if (this.state.page !== 1) {
+ filters.p = this.state.page;
+ }
+ if (this.state.query) {
+ filters.q = this.state.query;
+ }
+ return filters;
+ },
+
+ requestProjects() {
+ switch (this.state.type) {
+ case TYPE.ALL:
+ this.requestAllProjects();
+ break;
+ case TYPE.PROVISIONED:
+ this.requestProvisioned();
+ break;
+ case TYPE.GHOSTS:
+ this.requestGhosts();
+ break;
+ default:
+
+ // should never happen
+ }
+ },
+
+ requestGhosts() {
+ const data = this.getFilters();
+ getGhosts(data).then(r => {
+ let projects = r.projects.map(project => {
+ return _.extend(project, { id: project.uuid, qualifier: 'TRK' });
+ });
+ if (this.state.page > 1) {
+ projects = [].concat(this.state.projects, projects);
+ }
+ this.setState({ ready: true, projects, total: r.total });
+ });
+ },
+
+ requestProvisioned() {
+ const data = this.getFilters();
+ getProvisioned(data).then(r => {
+ let projects = r.projects.map(project => {
+ return _.extend(project, { id: project.uuid, qualifier: 'TRK' });
+ });
+ if (this.state.page > 1) {
+ projects = [].concat(this.state.projects, projects);
+ }
+ this.setState({ ready: true, projects, total: r.total });
+ });
+ },
+
+ requestAllProjects() {
+ const data = this.getFilters();
+ data.qualifiers = this.state.qualifiers;
+ getComponents(data).then(r => {
+ let projects = r.components;
+ if (this.state.page > 1) {
+ projects = [].concat(this.state.projects, projects);
+ }
+ this.setState({ ready: true, projects, total: r.paging.total });
+ });
+ },
+
+ loadMore() {
+ this.setState({ ready: false, page: this.state.page + 1 },
+ this.requestProjects);
+ },
+
+ onSearch(query) {
+ this.setState({
+ ready: false,
+ page: 1,
+ query,
+ selection: []
+ }, this.requestProjects);
+ },
+
+ onTypeChanged(newType) {
+ this.setState({
+ ready: false,
+ page: 1,
+ query: '',
+ type: newType,
+ qualifiers: 'TRK',
+ selection: []
+ }, this.requestProjects);
+ },
+
+ onQualifierChanged(newQualifier) {
+ this.setState({
+ ready: false,
+ page: 1,
+ query: '',
+ type: TYPE.ALL,
+ qualifiers: newQualifier,
+ selection: []
+ }, this.requestProjects);
+ },
+
+ onProjectSelected(project) {
+ const newSelection = _.uniq([].concat(this.state.selection, project.id));
+ this.setState({ selection: newSelection });
+ },
+
+ onProjectDeselected(project) {
+ const newSelection = _.without(this.state.selection, project.id);
+ this.setState({ selection: newSelection });
+ },
+
+ onAllSelected() {
+ const newSelection = this.state.projects.map(project => {
+ return project.id;
+ });
+ this.setState({ selection: newSelection });
+ },
+
+ onAllDeselected() {
+ this.setState({ selection: [] });
+ },
+
+ deleteProjects() {
+ this.setState({ ready: false });
+ const ids = this.state.selection.join(',');
+ deleteComponents({ ids }).then(() => {
+ this.setState({ page: 1, selection: [] }, this.requestProjects);
+ });
+ },
+
+ render() {
+ return (
+ <div className="page page-limited">
+ <Header
+ hasProvisionPermission={this.props.hasProvisionPermission}
+ selection={this.state.selection}
+ total={this.state.total}
+ query={this.state.query}
+ qualifier={this.state.qualifiers}
+ refresh={this.requestProjects}/>
+
+ <Search {...this.props} {...this.state}
+ onSearch={this.onSearch}
+ onTypeChanged={this.onTypeChanged}
+ onQualifierChanged={this.onQualifierChanged}
+ onAllSelected={this.onAllSelected}
+ onAllDeselected={this.onAllDeselected}
+ deleteProjects={this.deleteProjects}/>
+
+ <Projects
+ ready={this.state.ready}
+ projects={this.state.projects}
+ refresh={this.requestProjects}
+ selection={this.state.selection}
+ onProjectSelected={this.onProjectSelected}
+ onProjectDeselected={this.onProjectDeselected}/>
+
+ <ListFooter
+ ready={this.state.ready}
+ count={this.state.projects.length}
+ total={this.state.total}
+ loadMore={this.loadMore}/>
+ </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 classNames from 'classnames';
+import React from 'react';
+import {
+ getComponentUrl,
+ getComponentPermissionsUrl
+} from '../../helpers/urls';
+import ApplyTemplateView from '../permissions/project/views/ApplyTemplateView';
+import Checkbox from '../../components/controls/Checkbox';
+import QualifierIcon from '../../components/shared/qualifier-icon';
+import { translate } from '../../helpers/l10n';
+
+export default class Projects extends React.Component {
+ static propTypes = {
+ projects: React.PropTypes.array.isRequired,
+ selection: React.PropTypes.array.isRequired,
+ refresh: React.PropTypes.func.isRequired
+ };
+
+ componentWillMount () {
+ this.renderProject = this.renderProject.bind(this);
+ }
+
+ onProjectCheck (project, checked) {
+ if (checked) {
+ this.props.onProjectSelected(project);
+ } else {
+ this.props.onProjectDeselected(project);
+ }
+ }
+
+ onApplyTemplateClick (project, e) {
+ e.preventDefault();
+ e.target.blur();
+ new ApplyTemplateView({ project }).render();
+ }
+
+ isProjectSelected (project) {
+ return this.props.selection.indexOf(project.id) !== -1;
+ }
+
+ renderProject (project) {
+ const permissionsUrl = getComponentPermissionsUrl(project.key);
+
+ return (
+ <tr key={project.id}>
+ <td className="thin">
+ <Checkbox
+ checked={this.isProjectSelected(project)}
+ onCheck={this.onProjectCheck.bind(this, project)}/>
+ </td>
+ <td className="nowrap">
+ <a className="link-with-icon" href={getComponentUrl(project.key)}>
+ <QualifierIcon qualifier={project.qualifier}/>
+ {' '}
+ <span>{project.name}</span>
+ </a>
+ </td>
+ <td className="nowrap">
+ <span className="note">{project.key}</span>
+ </td>
+ <td className="thin nowrap">
+ <div className="dropdown">
+ <button className="dropdown-toggle" data-toggle="dropdown">
+ {translate('actions')}
+ {' '}
+ <i className="icon-dropdown"/>
+ </button>
+ <ul className="dropdown-menu dropdown-menu-right">
+ <li>
+ <a href={permissionsUrl}>
+ {translate('edit_permissions')}
+ </a>
+ </li>
+ <li>
+ <a href={permissionsUrl}
+ onClick={this.onApplyTemplateClick.bind(this, project)}>
+ {translate('projects_role.apply_template')}
+ </a>
+ </li>
+ </ul>
+ </div>
+ </td>
+ </tr>
+ );
+ }
+
+ render () {
+ const className = classNames('data', 'zebra',
+ { 'new-loading': !this.props.ready }
+ );
+
+ return (
+ <table className={className}>
+ <tbody>{this.props.projects.map(this.renderProject)}</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 _ from 'underscore';
+import React from 'react';
+import { TYPE, QUALIFIERS_ORDER } from './constants';
+import DeleteView from './delete-view';
+import RadioToggle from '../../components/controls/RadioToggle';
+import Checkbox from '../../components/controls/Checkbox';
+import { translate } from '../../helpers/l10n';
+
+export default React.createClass({
+ propTypes: {
+ onSearch: React.PropTypes.func.isRequired
+ },
+
+ onSubmit(e) {
+ e.preventDefault();
+ this.search();
+ },
+
+ search() {
+ const q = this.refs.input.value;
+ this.props.onSearch(q);
+ },
+
+ getTypeOptions() {
+ return [
+ { value: TYPE.ALL, label: 'All' },
+ { value: TYPE.PROVISIONED, label: 'Provisioned' },
+ { value: TYPE.GHOSTS, label: 'Ghosts' }
+ ];
+ },
+
+ getQualifierOptions() {
+ const options = this.props.topLevelQualifiers.map(q => {
+ return { value: q, label: translate('qualifiers', q) };
+ });
+ return _.sortBy(options, option => {
+ return QUALIFIERS_ORDER.indexOf(option.value);
+ });
+ },
+
+ renderCheckbox() {
+ const isAllChecked = this.props.projects.length > 0 &&
+ this.props.selection.length === this.props.projects.length;
+ const thirdState = this.props.projects.length > 0 &&
+ this.props.selection.length > 0 &&
+ this.props.selection.length < this.props.projects.length;
+ const checked = isAllChecked || thirdState;
+ return (
+ <Checkbox
+ checked={checked}
+ thirdState={thirdState}
+ onCheck={this.onCheck}/>
+ );
+ },
+
+ renderSpinner() {
+ return <i className="spinner"/>;
+ },
+
+ onCheck(checked) {
+ if (checked) {
+ this.props.onAllSelected();
+ } else {
+ this.props.onAllDeselected();
+ }
+ },
+
+ renderGhostsDescription () {
+ if (this.props.type !== TYPE.GHOSTS || !this.props.ready) {
+ return null;
+ }
+ return <div className="spacer-top alert alert-info">{translate('bulk_deletion.ghosts.description')}</div>;
+ },
+
+ deleteProjects() {
+ new DeleteView({
+ deleteProjects: this.props.deleteProjects
+ }).render();
+ },
+
+ renderQualifierFilter() {
+ const options = this.getQualifierOptions();
+ if (options.length < 2) {
+ return null;
+ }
+ return (
+ <td className="thin nowrap text-middle">
+ <RadioToggle
+ options={this.getQualifierOptions()}
+ value={this.props.qualifiers}
+ name="projects-qualifier"
+ onCheck={this.props.onQualifierChanged}/>
+ </td>
+ );
+ },
+
+ render() {
+ const isSomethingSelected = this.props.projects.length > 0 && this.props.selection.length > 0;
+ return (
+ <div className="panel panel-vertical bordered-bottom spacer-bottom">
+ <table className="data">
+ <tbody>
+ <tr>
+ <td className="thin text-middle">
+ {this.props.ready ? this.renderCheckbox() : this.renderSpinner()}
+ </td>
+ {this.renderQualifierFilter()}
+ <td className="thin nowrap text-middle">
+ <RadioToggle
+ options={this.getTypeOptions()}
+ value={this.props.type}
+ name="projects-type"
+ onCheck={this.props.onTypeChanged}/>
+ </td>
+ <td className="text-middle">
+ <form onSubmit={this.onSubmit} className="search-box">
+ <button className="search-box-submit button-clean">
+ <i className="icon-search"></i>
+ </button>
+ <input onChange={this.search}
+ value={this.props.query}
+ ref="input"
+ className="search-box-input"
+ type="search"
+ placeholder="Search"/>
+ </form>
+ </td>
+ <td className="thin text-middle">
+ <button onClick={this.deleteProjects} className="button-red"
+ disabled={!isSomethingSelected}>Delete
+ </button>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ {this.renderGhostsDescription()}
+ </div>
+ );
+ }
+});
--- /dev/null
+<form class="js-form" autocomplete="off">
+ <div class="modal-head">
+ <h2>Bulk Apply Permission Template</h2>
+ </div>
+
+ <div class="modal-body">
+ <div class="js-modal-messages"></div>
+
+ {{#if done}}
+ <div class="alert alert-success">
+ {{t 'projects_role.apply_template.success'}}
+ </div>
+ {{/if}}
+
+ {{#unless done}}
+ {{#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 class="modal-field">
+ <label>Apply To</label>
+ <ul style="padding-top: 4px;">
+ {{#if selectionTotal}}
+ <li>
+ <input value="selected" id="apply-to-selected" name="apply-to"
+ type="radio" checked>
+ <label
+ for="apply-to-selected"
+ style="float: none; left: 0; display: inline; padding: 0;">
+ Only Selected ({{selectionTotal}})
+ </label>
+ </li>
+ {{/if}}
+ <li>
+ <input value="all" id="apply-to-all" name="apply-to" type="radio"
+ {{#unless selectionTotal}}checked{{/unless}}>
+ <label
+ for="apply-to-all"
+ style="float: none; left: 0; display: inline; padding: 0;">
+ All ({{total}})
+ </label>
+ </li>
+ </ul>
+ </div>
+ {{/unless}}
+ </div>
+
+ <div class="modal-foot">
+ {{#unless done}}
+ {{#notNull permissionTemplates}}
+ <button class="js-apply">Apply</button>
+ {{/notNull}}
+ {{/unless}}
+ <a href="#" class="js-modal-close">Close</a>
+ </div>
+</form>
--- /dev/null
+<form id="create-project-form" autocomplete="off">
+ <div class="modal-head">
+ <h2>Create Project</h2>
+ </div>
+ <div class="modal-body">
+ <div class="js-modal-messages"></div>
+ {{#if createdProject}}
+ <div class="alert alert-success">
+ Project <a href="{{componentPermalink createdProject.k}}">{{createdProject.nm}}</a> has been successfully
+ created.
+ </div>
+ {{else}}
+ <div class="modal-field">
+ <label for="create-project-name">Name<em class="mandatory">*</em></label>
+ {{! keep this fake field to hack browser autofill }}
+ <input id="create-project-name-fake" name="name-fake" type="text" class="hidden">
+ <input id="create-project-name" name="name" type="text" maxlength="2000" required>
+ </div>
+ <div class="modal-field">
+ <label for="create-project-branch">Branch</label>
+ {{! keep this fake field to hack browser autofill }}
+ <input id="create-project-branch-fake" name="branch-fake" type="text" class="hidden">
+ <input id="create-project-branch" name="branch" type="text" maxlength="200">
+ </div>
+ <div class="modal-field">
+ <label for="create-project-key">Key<em class="mandatory">*</em></label>
+ {{! keep this fake field to hack browser autofill }}
+ <input id="create-project-key-fake" name="key-fake" type="text" class="hidden">
+ <input id="create-project-key" name="key" type="text" maxlength="400" required>
+ </div>
+ {{/if}}
+ </div>
+ <div class="modal-foot">
+ {{#if createdProject}}
+ <a href="#" class="js-modal-close">{{t 'close'}}</a>
+ {{else}}
+ <button id="create-project-submit">Create</button>
+ <a href="#" class="js-modal-close" id="create-project-cancel">Cancel</a>
+ {{/if}}
+ </div>
+</form>
--- /dev/null
+<form id="delete-project-form">
+ <div class="modal-head">
+ <h2>Delete Projects</h2>
+ </div>
+ <div class="modal-body">
+ <div class="js-modal-messages"></div>
+ Are you sure you want to delete selected projects?
+ </div>
+ <div class="modal-foot">
+ <button id="delete-project-submit" class="button-red">Delete</button>
+ <a href="#" class="js-modal-close" id="delete-project-cancel">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,
+ bulkApplyTemplate,
+ getPermissionTemplates
+} from '../../../api/permissions';
+import Template from '../templates/BulkApplyTemplateTemplate.hbs';
+
+export default ModalForm.extend({
+ template: Template,
+
+ initialize () {
+ this.loadPermissionTemplates();
+ this.done = false;
+ },
+
+ 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
+ });
+ },
+
+ bulkApplyToAll (permissionTemplate) {
+ const data = { templateId: permissionTemplate };
+
+ if (this.options.query) {
+ data.q = this.options.query;
+ }
+
+ if (this.options.qualifier) {
+ data.qualifier = this.options.qualifier;
+ }
+
+ return bulkApplyTemplate(data);
+ },
+
+ bulkApplyToSelected(permissionTemplate) {
+ const { selection } = this.options;
+ let lastRequest = Promise.resolve();
+
+ selection.forEach(projectId => {
+ const data = { templateId: permissionTemplate, projectId };
+ lastRequest = lastRequest.then(() => applyTemplateToProject(data));
+ });
+
+ return lastRequest;
+ },
+
+ onFormSubmit () {
+ ModalForm.prototype.onFormSubmit.apply(this, arguments);
+ const permissionTemplate = this.$('#project-permissions-template').val();
+ const applyTo = this.$('[name="apply-to"]:checked').val();
+ this.disableForm();
+
+ const request = applyTo === 'all' ?
+ this.bulkApplyToAll(permissionTemplate) :
+ this.bulkApplyToSelected(permissionTemplate);
+
+ request.then(() => {
+ this.trigger('done');
+ this.done = true;
+ this.render();
+ }).catch(function (e) {
+ e.response.json().then(r => {
+ this.showErrors(r.errors, r.warnings);
+ this.enableForm();
+ });
+ });
+ },
+
+ serializeData () {
+ return {
+ permissionTemplates: this.permissionTemplates,
+ selection: this.options.selection,
+ selectionTotal: this.options.selection.length,
+ total: this.options.total,
+ done: this.done
+ };
+ }
+});
+++ /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 { shallow } from 'enzyme';
-import Projects from '../projects';
-import Checkbox from '../../../components/controls/Checkbox';
-
-it('should render list of projects with no selection', () => {
- const projects = [
- { id: '1', key: 'a', name: 'A', qualifier: 'TRK' },
- { id: '2', key: 'b', name: 'B', qualifier: 'TRK' }
- ];
-
- const result = shallow(<Projects projects={projects} selection={[]} refresh={jest.fn()}/>);
- expect(result.find('tr').length).toBe(2);
- expect(result.find(Checkbox).filterWhere(n => n.prop('checked')).length).toBe(0);
-});
-
-it('should render list of projects with one selected', () => {
- const projects = [
- { id: '1', key: 'a', name: 'A', qualifier: 'TRK' },
- { id: '2', key: 'b', name: 'B', qualifier: 'TRK' }
- ];
- const selection = ['1'];
-
- const result = shallow(<Projects projects={projects} selection={selection} refresh={jest.fn()}/>);
- expect(result.find('tr').length).toBe(2);
- expect(result.find(Checkbox).filterWhere(n => n.prop('checked')).length).toBe(1);
-});
+++ /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';
-import { getCurrentUser } from '../../api/users';
-
-window.sonarqube.appStarted.then(options => {
- getCurrentUser().then(user => {
- const el = document.querySelector(options.el);
- const hasProvisionPermission = user.permissions.global.indexOf('provisioning') !== -1;
- const topLevelQualifiers = options.rootQualifiers;
- ReactDOM.render(<Main hasProvisionPermission={hasProvisionPermission}
- topLevelQualifiers={topLevelQualifiers}/>, 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.
- */
-export const PAGE_SIZE = 50;
-
-export const QUALIFIERS_ORDER = ['TRK', 'VW', 'DEV'];
-
-export const TYPE = {
- ALL: 'ALL',
- PROVISIONED: 'PROVISIONED',
- GHOSTS: 'GHOSTS'
-};
+++ /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 { createProject } from '../../api/components';
-import Template from './templates/projects-create-form.hbs';
-
-export default ModalForm.extend({
- template: Template,
-
- onRender () {
- ModalForm.prototype.onRender.apply(this, arguments);
- this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
- },
-
- onDestroy () {
- ModalForm.prototype.onDestroy.apply(this, arguments);
- this.$('[data-toggle="tooltip"]').tooltip('destroy');
- },
-
- onFormSubmit () {
- ModalForm.prototype.onFormSubmit.apply(this, arguments);
- this.sendRequest();
- },
-
- sendRequest () {
- const data = {
- name: this.$('#create-project-name').val(),
- branch: this.$('#create-project-branch').val(),
- key: this.$('#create-project-key').val()
- };
- this.disableForm();
- return createProject(data)
- .then(project => {
- if (this.options.refresh) {
- this.options.refresh();
- }
- this.enableForm();
- this.createdProject = project;
- this.render();
- })
- .catch(error => {
- this.enableForm();
- if (error.response.status === 400) {
- error.response.json().then(obj => this.showErrors([{ msg: obj.err_msg }]));
- }
- });
- },
-
- serializeData () {
- return {
- ...ModalForm.prototype.serializeData.apply(this, arguments),
- createdProject: this.createdProject
- };
- }
-});
+++ /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 Template from './templates/projects-delete.hbs';
-
-export default ModalForm.extend({
- template: Template,
-
- onFormSubmit () {
- ModalForm.prototype.onFormSubmit.apply(this, arguments);
- this.options.deleteProjects();
- this.destroy();
- }
-});
-
+++ /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';
-
-export default ModalForm.extend({
-
- onRender () {
- ModalForm.prototype.onRender.apply(this, arguments);
- this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
- },
-
- onDestroy () {
- ModalForm.prototype.onDestroy.apply(this, arguments);
- this.$('[data-toggle="tooltip"]').tooltip('destroy');
- },
-
- onFormSubmit () {
- ModalForm.prototype.onFormSubmit.apply(this, arguments);
- this.sendRequest();
- }
-
-});
+++ /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 CreateView from './create-view';
-import BulkApplyTemplateView from './views/BulkApplyTemplateView';
-
-export default class Header extends React.Component {
- static propTypes = {
- hasProvisionPermission: React.PropTypes.bool.isRequired
- };
-
- createProject () {
- new CreateView({
- refresh: this.props.refresh
- }).render();
- }
-
- bulkApplyTemplate () {
- new BulkApplyTemplateView({
- total: this.props.total,
- selection: this.props.selection,
- query: this.props.query,
- qualifier: this.props.qualifier
- }).render();
- }
-
- renderCreateButton () {
- if (!this.props.hasProvisionPermission) {
- return null;
- }
- return (
- <li>
- <button onClick={this.createProject.bind(this)}>
- Create Project
- </button>
- </li>
- );
- }
-
- renderBulkApplyTemplateButton () {
- return (
- <li>
- <button onClick={this.bulkApplyTemplate.bind(this)}>
- Bulk Apply Permission Template
- </button>
- </li>
- );
- }
-
- render () {
- return (
- <header className="page-header">
- <h1 className="page-title">Projects Management</h1>
- <div className="page-actions">
- <ul className="list-inline">
- {this.renderCreateButton()}
- {this.renderBulkApplyTemplateButton()}
- </ul>
- </div>
- <p className="page-description">Use this page to delete multiple projects at once, or to provision projects
- if you would like to configure them before the first analysis. Note that once a project is provisioned, you
- have access to perform all project configurations on it.</p>
- </header>
- );
- }
-}
+++ /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 Header from './header';
-import Search from './search';
-import Projects from './projects';
-import { PAGE_SIZE, TYPE } from './constants';
-import {
- getComponents,
- getProvisioned,
- getGhosts,
- deleteComponents
-} from '../../api/components';
-import ListFooter from '../../components/controls/ListFooter';
-
-export default React.createClass({
- propTypes: {
- hasProvisionPermission: React.PropTypes.bool.isRequired,
- topLevelQualifiers: React.PropTypes.array.isRequired
- },
-
- getInitialState() {
- return {
- ready: false,
- projects: [],
- total: 0,
- page: 1,
- query: '',
- qualifiers: 'TRK',
- type: TYPE.ALL,
- selection: []
- };
- },
-
- componentWillMount () {
- this.requestProjects = _.debounce(this.requestProjects, 250);
- },
-
- componentDidMount() {
- this.requestProjects();
- },
-
- getFilters() {
- const filters = { ps: PAGE_SIZE };
- if (this.state.page !== 1) {
- filters.p = this.state.page;
- }
- if (this.state.query) {
- filters.q = this.state.query;
- }
- return filters;
- },
-
- requestProjects() {
- switch (this.state.type) {
- case TYPE.ALL:
- this.requestAllProjects();
- break;
- case TYPE.PROVISIONED:
- this.requestProvisioned();
- break;
- case TYPE.GHOSTS:
- this.requestGhosts();
- break;
- default:
-
- // should never happen
- }
- },
-
- requestGhosts() {
- const data = this.getFilters();
- getGhosts(data).then(r => {
- let projects = r.projects.map(project => {
- return _.extend(project, { id: project.uuid, qualifier: 'TRK' });
- });
- if (this.state.page > 1) {
- projects = [].concat(this.state.projects, projects);
- }
- this.setState({ ready: true, projects, total: r.total });
- });
- },
-
- requestProvisioned() {
- const data = this.getFilters();
- getProvisioned(data).then(r => {
- let projects = r.projects.map(project => {
- return _.extend(project, { id: project.uuid, qualifier: 'TRK' });
- });
- if (this.state.page > 1) {
- projects = [].concat(this.state.projects, projects);
- }
- this.setState({ ready: true, projects, total: r.total });
- });
- },
-
- requestAllProjects() {
- const data = this.getFilters();
- data.qualifiers = this.state.qualifiers;
- getComponents(data).then(r => {
- let projects = r.components;
- if (this.state.page > 1) {
- projects = [].concat(this.state.projects, projects);
- }
- this.setState({ ready: true, projects, total: r.paging.total });
- });
- },
-
- loadMore() {
- this.setState({ ready: false, page: this.state.page + 1 },
- this.requestProjects);
- },
-
- onSearch(query) {
- this.setState({
- ready: false,
- page: 1,
- query,
- selection: []
- }, this.requestProjects);
- },
-
- onTypeChanged(newType) {
- this.setState({
- ready: false,
- page: 1,
- query: '',
- type: newType,
- qualifiers: 'TRK',
- selection: []
- }, this.requestProjects);
- },
-
- onQualifierChanged(newQualifier) {
- this.setState({
- ready: false,
- page: 1,
- query: '',
- type: TYPE.ALL,
- qualifiers: newQualifier,
- selection: []
- }, this.requestProjects);
- },
-
- onProjectSelected(project) {
- const newSelection = _.uniq([].concat(this.state.selection, project.id));
- this.setState({ selection: newSelection });
- },
-
- onProjectDeselected(project) {
- const newSelection = _.without(this.state.selection, project.id);
- this.setState({ selection: newSelection });
- },
-
- onAllSelected() {
- const newSelection = this.state.projects.map(project => {
- return project.id;
- });
- this.setState({ selection: newSelection });
- },
-
- onAllDeselected() {
- this.setState({ selection: [] });
- },
-
- deleteProjects() {
- this.setState({ ready: false });
- const ids = this.state.selection.join(',');
- deleteComponents({ ids }).then(() => {
- this.setState({ page: 1, selection: [] }, this.requestProjects);
- });
- },
-
- render() {
- return (
- <div className="page page-limited">
- <Header
- hasProvisionPermission={this.props.hasProvisionPermission}
- selection={this.state.selection}
- total={this.state.total}
- query={this.state.query}
- qualifier={this.state.qualifiers}
- refresh={this.requestProjects}/>
-
- <Search {...this.props} {...this.state}
- onSearch={this.onSearch}
- onTypeChanged={this.onTypeChanged}
- onQualifierChanged={this.onQualifierChanged}
- onAllSelected={this.onAllSelected}
- onAllDeselected={this.onAllDeselected}
- deleteProjects={this.deleteProjects}/>
-
- <Projects
- ready={this.state.ready}
- projects={this.state.projects}
- refresh={this.requestProjects}
- selection={this.state.selection}
- onProjectSelected={this.onProjectSelected}
- onProjectDeselected={this.onProjectDeselected}/>
-
- <ListFooter
- ready={this.state.ready}
- count={this.state.projects.length}
- total={this.state.total}
- loadMore={this.loadMore}/>
- </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 classNames from 'classnames';
-import React from 'react';
-import {
- getComponentUrl,
- getComponentPermissionsUrl
-} from '../../helpers/urls';
-import ApplyTemplateView from '../permissions/project/views/ApplyTemplateView';
-import Checkbox from '../../components/controls/Checkbox';
-import QualifierIcon from '../../components/shared/qualifier-icon';
-import { translate } from '../../helpers/l10n';
-
-export default class Projects extends React.Component {
- static propTypes = {
- projects: React.PropTypes.array.isRequired,
- selection: React.PropTypes.array.isRequired,
- refresh: React.PropTypes.func.isRequired
- };
-
- componentWillMount () {
- this.renderProject = this.renderProject.bind(this);
- }
-
- onProjectCheck (project, checked) {
- if (checked) {
- this.props.onProjectSelected(project);
- } else {
- this.props.onProjectDeselected(project);
- }
- }
-
- onApplyTemplateClick (project, e) {
- e.preventDefault();
- e.target.blur();
- new ApplyTemplateView({ project }).render();
- }
-
- isProjectSelected (project) {
- return this.props.selection.indexOf(project.id) !== -1;
- }
-
- renderProject (project) {
- const permissionsUrl = getComponentPermissionsUrl(project.key);
-
- return (
- <tr key={project.id}>
- <td className="thin">
- <Checkbox
- checked={this.isProjectSelected(project)}
- onCheck={this.onProjectCheck.bind(this, project)}/>
- </td>
- <td className="nowrap">
- <a className="link-with-icon" href={getComponentUrl(project.key)}>
- <QualifierIcon qualifier={project.qualifier}/>
- {' '}
- <span>{project.name}</span>
- </a>
- </td>
- <td className="nowrap">
- <span className="note">{project.key}</span>
- </td>
- <td className="thin nowrap">
- <div className="dropdown">
- <button className="dropdown-toggle" data-toggle="dropdown">
- {translate('actions')}
- {' '}
- <i className="icon-dropdown"/>
- </button>
- <ul className="dropdown-menu dropdown-menu-right">
- <li>
- <a href={permissionsUrl}>
- {translate('edit_permissions')}
- </a>
- </li>
- <li>
- <a href={permissionsUrl}
- onClick={this.onApplyTemplateClick.bind(this, project)}>
- {translate('projects_role.apply_template')}
- </a>
- </li>
- </ul>
- </div>
- </td>
- </tr>
- );
- }
-
- render () {
- const className = classNames('data', 'zebra',
- { 'new-loading': !this.props.ready }
- );
-
- return (
- <table className={className}>
- <tbody>{this.props.projects.map(this.renderProject)}</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 _ from 'underscore';
-import React from 'react';
-import { TYPE, QUALIFIERS_ORDER } from './constants';
-import DeleteView from './delete-view';
-import RadioToggle from '../../components/controls/RadioToggle';
-import Checkbox from '../../components/controls/Checkbox';
-import { translate } from '../../helpers/l10n';
-
-export default React.createClass({
- propTypes: {
- onSearch: React.PropTypes.func.isRequired
- },
-
- onSubmit(e) {
- e.preventDefault();
- this.search();
- },
-
- search() {
- const q = this.refs.input.value;
- this.props.onSearch(q);
- },
-
- getTypeOptions() {
- return [
- { value: TYPE.ALL, label: 'All' },
- { value: TYPE.PROVISIONED, label: 'Provisioned' },
- { value: TYPE.GHOSTS, label: 'Ghosts' }
- ];
- },
-
- getQualifierOptions() {
- const options = this.props.topLevelQualifiers.map(q => {
- return { value: q, label: translate('qualifiers', q) };
- });
- return _.sortBy(options, option => {
- return QUALIFIERS_ORDER.indexOf(option.value);
- });
- },
-
- renderCheckbox() {
- const isAllChecked = this.props.projects.length > 0 &&
- this.props.selection.length === this.props.projects.length;
- const thirdState = this.props.projects.length > 0 &&
- this.props.selection.length > 0 &&
- this.props.selection.length < this.props.projects.length;
- const checked = isAllChecked || thirdState;
- return (
- <Checkbox
- checked={checked}
- thirdState={thirdState}
- onCheck={this.onCheck}/>
- );
- },
-
- renderSpinner() {
- return <i className="spinner"/>;
- },
-
- onCheck(checked) {
- if (checked) {
- this.props.onAllSelected();
- } else {
- this.props.onAllDeselected();
- }
- },
-
- renderGhostsDescription () {
- if (this.props.type !== TYPE.GHOSTS || !this.props.ready) {
- return null;
- }
- return <div className="spacer-top alert alert-info">{translate('bulk_deletion.ghosts.description')}</div>;
- },
-
- deleteProjects() {
- new DeleteView({
- deleteProjects: this.props.deleteProjects
- }).render();
- },
-
- renderQualifierFilter() {
- const options = this.getQualifierOptions();
- if (options.length < 2) {
- return null;
- }
- return (
- <td className="thin nowrap text-middle">
- <RadioToggle
- options={this.getQualifierOptions()}
- value={this.props.qualifiers}
- name="projects-qualifier"
- onCheck={this.props.onQualifierChanged}/>
- </td>
- );
- },
-
- render() {
- const isSomethingSelected = this.props.projects.length > 0 && this.props.selection.length > 0;
- return (
- <div className="panel panel-vertical bordered-bottom spacer-bottom">
- <table className="data">
- <tbody>
- <tr>
- <td className="thin text-middle">
- {this.props.ready ? this.renderCheckbox() : this.renderSpinner()}
- </td>
- {this.renderQualifierFilter()}
- <td className="thin nowrap text-middle">
- <RadioToggle
- options={this.getTypeOptions()}
- value={this.props.type}
- name="projects-type"
- onCheck={this.props.onTypeChanged}/>
- </td>
- <td className="text-middle">
- <form onSubmit={this.onSubmit} className="search-box">
- <button className="search-box-submit button-clean">
- <i className="icon-search"></i>
- </button>
- <input onChange={this.search}
- value={this.props.query}
- ref="input"
- className="search-box-input"
- type="search"
- placeholder="Search"/>
- </form>
- </td>
- <td className="thin text-middle">
- <button onClick={this.deleteProjects} className="button-red"
- disabled={!isSomethingSelected}>Delete
- </button>
- </td>
- </tr>
- </tbody>
- </table>
- {this.renderGhostsDescription()}
- </div>
- );
- }
-});
+++ /dev/null
-<form class="js-form" autocomplete="off">
- <div class="modal-head">
- <h2>Bulk Apply Permission Template</h2>
- </div>
-
- <div class="modal-body">
- <div class="js-modal-messages"></div>
-
- {{#if done}}
- <div class="alert alert-success">
- {{t 'projects_role.apply_template.success'}}
- </div>
- {{/if}}
-
- {{#unless done}}
- {{#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 class="modal-field">
- <label>Apply To</label>
- <ul style="padding-top: 4px;">
- {{#if selectionTotal}}
- <li>
- <input value="selected" id="apply-to-selected" name="apply-to"
- type="radio" checked>
- <label
- for="apply-to-selected"
- style="float: none; left: 0; display: inline; padding: 0;">
- Only Selected ({{selectionTotal}})
- </label>
- </li>
- {{/if}}
- <li>
- <input value="all" id="apply-to-all" name="apply-to" type="radio"
- {{#unless selectionTotal}}checked{{/unless}}>
- <label
- for="apply-to-all"
- style="float: none; left: 0; display: inline; padding: 0;">
- All ({{total}})
- </label>
- </li>
- </ul>
- </div>
- {{/unless}}
- </div>
-
- <div class="modal-foot">
- {{#unless done}}
- {{#notNull permissionTemplates}}
- <button class="js-apply">Apply</button>
- {{/notNull}}
- {{/unless}}
- <a href="#" class="js-modal-close">Close</a>
- </div>
-</form>
+++ /dev/null
-<form id="create-project-form" autocomplete="off">
- <div class="modal-head">
- <h2>Create Project</h2>
- </div>
- <div class="modal-body">
- <div class="js-modal-messages"></div>
- {{#if createdProject}}
- <div class="alert alert-success">
- Project <a href="{{componentPermalink createdProject.k}}">{{createdProject.nm}}</a> has been successfully
- created.
- </div>
- {{else}}
- <div class="modal-field">
- <label for="create-project-name">Name<em class="mandatory">*</em></label>
- {{! keep this fake field to hack browser autofill }}
- <input id="create-project-name-fake" name="name-fake" type="text" class="hidden">
- <input id="create-project-name" name="name" type="text" maxlength="2000" required>
- </div>
- <div class="modal-field">
- <label for="create-project-branch">Branch</label>
- {{! keep this fake field to hack browser autofill }}
- <input id="create-project-branch-fake" name="branch-fake" type="text" class="hidden">
- <input id="create-project-branch" name="branch" type="text" maxlength="200">
- </div>
- <div class="modal-field">
- <label for="create-project-key">Key<em class="mandatory">*</em></label>
- {{! keep this fake field to hack browser autofill }}
- <input id="create-project-key-fake" name="key-fake" type="text" class="hidden">
- <input id="create-project-key" name="key" type="text" maxlength="400" required>
- </div>
- {{/if}}
- </div>
- <div class="modal-foot">
- {{#if createdProject}}
- <a href="#" class="js-modal-close">{{t 'close'}}</a>
- {{else}}
- <button id="create-project-submit">Create</button>
- <a href="#" class="js-modal-close" id="create-project-cancel">Cancel</a>
- {{/if}}
- </div>
-</form>
+++ /dev/null
-<form id="delete-project-form">
- <div class="modal-head">
- <h2>Delete Projects</h2>
- </div>
- <div class="modal-body">
- <div class="js-modal-messages"></div>
- Are you sure you want to delete selected projects?
- </div>
- <div class="modal-foot">
- <button id="delete-project-submit" class="button-red">Delete</button>
- <a href="#" class="js-modal-close" id="delete-project-cancel">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,
- bulkApplyTemplate,
- getPermissionTemplates
-} from '../../../api/permissions';
-import Template from '../templates/BulkApplyTemplateTemplate.hbs';
-
-export default ModalForm.extend({
- template: Template,
-
- initialize () {
- this.loadPermissionTemplates();
- this.done = false;
- },
-
- 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
- });
- },
-
- bulkApplyToAll (permissionTemplate) {
- const data = { templateId: permissionTemplate };
-
- if (this.options.query) {
- data.q = this.options.query;
- }
-
- if (this.options.qualifier) {
- data.qualifier = this.options.qualifier;
- }
-
- return bulkApplyTemplate(data);
- },
-
- bulkApplyToSelected(permissionTemplate) {
- const { selection } = this.options;
- let lastRequest = Promise.resolve();
-
- selection.forEach(projectId => {
- const data = { templateId: permissionTemplate, projectId };
- lastRequest = lastRequest.then(() => applyTemplateToProject(data));
- });
-
- return lastRequest;
- },
-
- onFormSubmit () {
- ModalForm.prototype.onFormSubmit.apply(this, arguments);
- const permissionTemplate = this.$('#project-permissions-template').val();
- const applyTo = this.$('[name="apply-to"]:checked').val();
- this.disableForm();
-
- const request = applyTo === 'all' ?
- this.bulkApplyToAll(permissionTemplate) :
- this.bulkApplyToSelected(permissionTemplate);
-
- request.then(() => {
- this.trigger('done');
- this.done = true;
- this.render();
- }).catch(function (e) {
- e.response.json().then(r => {
- this.showErrors(r.errors, r.warnings);
- this.enableForm();
- });
- });
- },
-
- serializeData () {
- return {
- permissionTemplates: this.permissionTemplates,
- selection: this.options.selection,
- selectionTotal: this.options.selection.length,
- total: this.options.total,
- done: this.done
- };
- }
-});
},
isProjectsActive() {
- const urls = ['/projects', '/background_tasks'];
+ const urls = ['/projects_admin', '/background_tasks'];
return this.isSomethingActive(urls);
},
<i className="icon-dropdown"></i>
</a>
<ul className="dropdown-menu">
- {this.renderLink('/projects', 'Management')}
+ {this.renderLink('/projects_admin', 'Management')}
{this.renderLink('/background_tasks',
translate('background_tasks.page'))}
</ul>
--- /dev/null
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2014 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# SonarQube 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.
+#
+# SonarQube 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.
+#
+class ProjectsAdminController < ApplicationController
+
+ before_filter :admin_required
+
+ SECTION=Navigation::SECTION_CONFIGURATION
+
+ def index
+
+ end
+
+end
+++ /dev/null
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2014 SonarSource
-# mailto:contact AT sonarsource DOT com
-#
-# SonarQube 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.
-#
-# SonarQube 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.
-#
-class ProjectsController < ApplicationController
-
- before_filter :admin_required
-
- SECTION=Navigation::SECTION_CONFIGURATION
-
- def index
-
- end
-
-end
+++ /dev/null
-<% content_for :extra_script do %>
- <script src="<%= ApplicationController.root_context -%>/js/bundles/projects.js?v=<%= sonar_version -%>"></script>
-<% end %>
--- /dev/null
+<% content_for :extra_script do %>
+ <script src="<%= ApplicationController.root_context -%>/js/bundles/projects-admin.js?v=<%= sonar_version -%>"></script>
+<% end %>