]> source.dussan.org Git - sonarqube.git/commitdiff
move /projects to /projects/admin
authorStas Vilchik <vilchiks@gmail.com>
Mon, 17 Oct 2016 09:37:50 +0000 (11:37 +0200)
committerStas Vilchik <vilchiks@gmail.com>
Fri, 21 Oct 2016 08:24:17 +0000 (10:24 +0200)
37 files changed:
it/it-tests/src/test/resources/authorisation/ProvisioningPermissionTest/should-be-able-to-provision-project.html
it/it-tests/src/test/resources/authorisation/ProvisioningPermissionTest/should-not-be-able-to-provision-project.html
it/it-tests/src/test/resources/projectAdministration/BulkDeletionTest/bulk-delete-filter-projects.html
server/sonar-web/config/webpack/webpack.config.base.js
server/sonar-web/src/main/js/apps/projects-admin/__tests__/projects-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/app.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/constants.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/create-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/delete-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/form-view.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/header.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/main.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/projects.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/search.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/templates/BulkApplyTemplateTemplate.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/templates/projects-create-form.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/templates/projects-delete.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/views/BulkApplyTemplateView.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/__tests__/projects-test.js [deleted file]
server/sonar-web/src/main/js/apps/projects/app.js [deleted file]
server/sonar-web/src/main/js/apps/projects/constants.js [deleted file]
server/sonar-web/src/main/js/apps/projects/create-view.js [deleted file]
server/sonar-web/src/main/js/apps/projects/delete-view.js [deleted file]
server/sonar-web/src/main/js/apps/projects/form-view.js [deleted file]
server/sonar-web/src/main/js/apps/projects/header.js [deleted file]
server/sonar-web/src/main/js/apps/projects/main.js [deleted file]
server/sonar-web/src/main/js/apps/projects/projects.js [deleted file]
server/sonar-web/src/main/js/apps/projects/search.js [deleted file]
server/sonar-web/src/main/js/apps/projects/templates/BulkApplyTemplateTemplate.hbs [deleted file]
server/sonar-web/src/main/js/apps/projects/templates/projects-create-form.hbs [deleted file]
server/sonar-web/src/main/js/apps/projects/templates/projects-delete.hbs [deleted file]
server/sonar-web/src/main/js/apps/projects/views/BulkApplyTemplateView.js [deleted file]
server/sonar-web/src/main/js/main/nav/settings/settings-nav.js
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/projects_admin_controller.rb [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/projects_controller.rb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/projects/index.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/projects_admin/index.html.erb [new file with mode: 0644]

index b1a78ffe504c9994eab89a9399e0ed9227aa665d..3752d35ee362d279c0c1baa5c969084020f9f243 100644 (file)
@@ -40,7 +40,7 @@
 </tr>
 <tr>
        <td>open</td>
-       <td>/projects</td>
+       <td>/projects_admin</td>
        <td></td>
 </tr>
 <tr>
index 245c6ba7ca4e0cfdb8bf534ab4afaacc56bde97a..de369bbcb09bfd8e502f9d50a612bde6e47a24ba 100644 (file)
@@ -43,7 +43,7 @@ module.exports = {
     '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',
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/__tests__/projects-test.js b/server/sonar-web/src/main/js/apps/projects-admin/__tests__/projects-test.js
new file mode 100644 (file)
index 0000000..85abb70
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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);
+});
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/app.js b/server/sonar-web/src/main/js/apps/projects-admin/app.js
new file mode 100644 (file)
index 0000000..7c68d2c
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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);
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/constants.js b/server/sonar-web/src/main/js/apps/projects-admin/constants.js
new file mode 100644 (file)
index 0000000..59a57a2
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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'
+};
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/create-view.js b/server/sonar-web/src/main/js/apps/projects-admin/create-view.js
new file mode 100644 (file)
index 0000000..3f16623
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * 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
+    };
+  }
+});
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/delete-view.js b/server/sonar-web/src/main/js/apps/projects-admin/delete-view.js
new file mode 100644 (file)
index 0000000..7d7ee00
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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();
+  }
+});
+
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/form-view.js b/server/sonar-web/src/main/js/apps/projects-admin/form-view.js
new file mode 100644 (file)
index 0000000..7b96a15
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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();
+  }
+
+});
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/header.js b/server/sonar-web/src/main/js/apps/projects-admin/header.js
new file mode 100644 (file)
index 0000000..1d4200e
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/main.js b/server/sonar-web/src/main/js/apps/projects-admin/main.js
new file mode 100644 (file)
index 0000000..c3a240b
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * 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>
+    );
+  }
+});
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/projects.js b/server/sonar-web/src/main/js/apps/projects-admin/projects.js
new file mode 100644 (file)
index 0000000..140471d
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * 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>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/search.js b/server/sonar-web/src/main/js/apps/projects-admin/search.js
new file mode 100644 (file)
index 0000000..24817c9
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * 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>
+    );
+  }
+});
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/templates/BulkApplyTemplateTemplate.hbs b/server/sonar-web/src/main/js/apps/projects-admin/templates/BulkApplyTemplateTemplate.hbs
new file mode 100644 (file)
index 0000000..b4933eb
--- /dev/null
@@ -0,0 +1,68 @@
+<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>
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/templates/projects-create-form.hbs b/server/sonar-web/src/main/js/apps/projects-admin/templates/projects-create-form.hbs
new file mode 100644 (file)
index 0000000..f94b546
--- /dev/null
@@ -0,0 +1,41 @@
+<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>
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/templates/projects-delete.hbs b/server/sonar-web/src/main/js/apps/projects-admin/templates/projects-delete.hbs
new file mode 100644 (file)
index 0000000..2ab69b2
--- /dev/null
@@ -0,0 +1,13 @@
+<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>
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/views/BulkApplyTemplateView.js b/server/sonar-web/src/main/js/apps/projects-admin/views/BulkApplyTemplateView.js
new file mode 100644 (file)
index 0000000..d2fc1ab
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * 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
+    };
+  }
+});
diff --git a/server/sonar-web/src/main/js/apps/projects/__tests__/projects-test.js b/server/sonar-web/src/main/js/apps/projects/__tests__/projects-test.js
deleted file mode 100644 (file)
index 85abb70..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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);
-});
diff --git a/server/sonar-web/src/main/js/apps/projects/app.js b/server/sonar-web/src/main/js/apps/projects/app.js
deleted file mode 100644 (file)
index 7c68d2c..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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);
-  });
-});
diff --git a/server/sonar-web/src/main/js/apps/projects/constants.js b/server/sonar-web/src/main/js/apps/projects/constants.js
deleted file mode 100644 (file)
index 59a57a2..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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'
-};
diff --git a/server/sonar-web/src/main/js/apps/projects/create-view.js b/server/sonar-web/src/main/js/apps/projects/create-view.js
deleted file mode 100644 (file)
index 3f16623..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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
-    };
-  }
-});
diff --git a/server/sonar-web/src/main/js/apps/projects/delete-view.js b/server/sonar-web/src/main/js/apps/projects/delete-view.js
deleted file mode 100644 (file)
index 7d7ee00..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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();
-  }
-});
-
diff --git a/server/sonar-web/src/main/js/apps/projects/form-view.js b/server/sonar-web/src/main/js/apps/projects/form-view.js
deleted file mode 100644 (file)
index 7b96a15..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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();
-  }
-
-});
diff --git a/server/sonar-web/src/main/js/apps/projects/header.js b/server/sonar-web/src/main/js/apps/projects/header.js
deleted file mode 100644 (file)
index 1d4200e..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/main.js b/server/sonar-web/src/main/js/apps/projects/main.js
deleted file mode 100644 (file)
index c3a240b..0000000
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * 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>
-    );
-  }
-});
diff --git a/server/sonar-web/src/main/js/apps/projects/projects.js b/server/sonar-web/src/main/js/apps/projects/projects.js
deleted file mode 100644 (file)
index 140471d..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/search.js b/server/sonar-web/src/main/js/apps/projects/search.js
deleted file mode 100644 (file)
index 24817c9..0000000
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * 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>
-    );
-  }
-});
diff --git a/server/sonar-web/src/main/js/apps/projects/templates/BulkApplyTemplateTemplate.hbs b/server/sonar-web/src/main/js/apps/projects/templates/BulkApplyTemplateTemplate.hbs
deleted file mode 100644 (file)
index b4933eb..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<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>
diff --git a/server/sonar-web/src/main/js/apps/projects/templates/projects-create-form.hbs b/server/sonar-web/src/main/js/apps/projects/templates/projects-create-form.hbs
deleted file mode 100644 (file)
index f94b546..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<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>
diff --git a/server/sonar-web/src/main/js/apps/projects/templates/projects-delete.hbs b/server/sonar-web/src/main/js/apps/projects/templates/projects-delete.hbs
deleted file mode 100644 (file)
index 2ab69b2..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<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>
diff --git a/server/sonar-web/src/main/js/apps/projects/views/BulkApplyTemplateView.js b/server/sonar-web/src/main/js/apps/projects/views/BulkApplyTemplateView.js
deleted file mode 100644 (file)
index d2fc1ab..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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
-    };
-  }
-});
index afad572d0e8d991831b95bae90417590cbc3ec63..35313c303003445ae99baf248b55a03e70d1aabd 100644 (file)
@@ -41,7 +41,7 @@ export default React.createClass({
   },
 
   isProjectsActive() {
-    const urls = ['/projects', '/background_tasks'];
+    const urls = ['/projects_admin', '/background_tasks'];
     return this.isSomethingActive(urls);
   },
 
@@ -109,7 +109,7 @@ export default React.createClass({
                 <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>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/projects_admin_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/projects_admin_controller.rb
new file mode 100644 (file)
index 0000000..09ad738
--- /dev/null
@@ -0,0 +1,30 @@
+#
+# 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
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/projects_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/projects_controller.rb
deleted file mode 100644 (file)
index 6661201..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# 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
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/projects/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/projects/index.html.erb
deleted file mode 100644 (file)
index e9c8ffd..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-<% content_for :extra_script do %>
-  <script src="<%= ApplicationController.root_context -%>/js/bundles/projects.js?v=<%= sonar_version -%>"></script>
-<% end %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/projects_admin/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/projects_admin/index.html.erb
new file mode 100644 (file)
index 0000000..3c6e21d
--- /dev/null
@@ -0,0 +1,3 @@
+<% content_for :extra_script do %>
+  <script src="<%= ApplicationController.root_context -%>/js/bundles/projects-admin.js?v=<%= sonar_version -%>"></script>
+<% end %>