@@ -26,6 +26,7 @@ import org.junit.AfterClass; | |||
import org.junit.Before; | |||
import org.junit.BeforeClass; | |||
import org.junit.ClassRule; | |||
import org.junit.Ignore; | |||
import org.junit.Test; | |||
import org.sonar.wsclient.issue.BulkChangeQuery; | |||
import org.sonar.wsclient.issue.Issue; | |||
@@ -42,6 +43,7 @@ import static util.ItUtils.runProjectAnalysis; | |||
import static util.ItUtils.setServerProperty; | |||
import static util.selenium.Selenese.runSelenese; | |||
@Ignore("notifications page is not available yet") | |||
public class IssueNotificationsTest extends AbstractIssueTest { | |||
private final static String PROJECT_KEY = "sample"; |
@@ -310,6 +310,7 @@ public class IssueSearchTest extends AbstractIssueTest { | |||
} | |||
@Test | |||
@Ignore("bulk change form is not available yet") | |||
public void bulk_change() { | |||
runSelenese(ORCHESTRATOR, "/issue/IssueSearchTest/bulk_change.html"); | |||
} |
@@ -127,8 +127,8 @@ public class ProjectAdministrationTest { | |||
} | |||
// SONAR-4203 | |||
@Ignore("UUID column added in Events page") | |||
@Test | |||
@Ignore("history page is not available yet") | |||
public void delete_version_of_multimodule_project() { | |||
GregorianCalendar today = new GregorianCalendar(); | |||
SonarScanner build = SonarScanner.create(projectDir("shared/xoo-multi-modules-sample")) |
@@ -39,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; | |||
import static util.ItUtils.projectDir; | |||
import static util.selenium.Selenese.runSelenese; | |||
@Ignore("history page is not available yet") | |||
public class EventTest { | |||
@ClassRule |
@@ -28,6 +28,7 @@ import org.junit.AfterClass; | |||
import org.junit.Before; | |||
import org.junit.BeforeClass; | |||
import org.junit.ClassRule; | |||
import org.junit.Ignore; | |||
import org.junit.Test; | |||
import org.sonar.wsclient.Sonar; | |||
import org.sonar.wsclient.qualitygate.NewCondition; | |||
@@ -78,6 +79,7 @@ public class QualityGateNotificationTest { | |||
} | |||
@Test | |||
@Ignore("notifications page is not available yet") | |||
public void status_on_metric_variation_and_send_notifications() throws Exception { | |||
Wiser smtpServer = new Wiser(0); | |||
try { |
@@ -27,6 +27,7 @@ import org.junit.AfterClass; | |||
import org.junit.Before; | |||
import org.junit.BeforeClass; | |||
import org.junit.ClassRule; | |||
import org.junit.Ignore; | |||
import org.junit.Test; | |||
import org.sonar.wsclient.qualitygate.NewCondition; | |||
import org.sonar.wsclient.qualitygate.QualityGate; | |||
@@ -69,6 +70,7 @@ public class QualityGateUiTest { | |||
* SONAR-3326 | |||
*/ | |||
@Test | |||
@Ignore("history page is not available yet") | |||
public void display_alerts_correctly_in_history_page() { | |||
QualityGateClient qgClient = qgClient(); | |||
QualityGate qGate = qgClient.create("AlertsForHistory"); |
@@ -23,6 +23,7 @@ import com.sonar.orchestrator.Orchestrator; | |||
import it.Category4Suite; | |||
import org.junit.BeforeClass; | |||
import org.junit.ClassRule; | |||
import org.junit.Ignore; | |||
import org.junit.Test; | |||
import static util.selenium.Selenese.runSelenese; | |||
@@ -48,6 +49,7 @@ public class UiExtensionsTest { | |||
* SONAR-2376 | |||
*/ | |||
@Test | |||
@Ignore("page extensions are not reimplemented yet") | |||
public void test_page_decoration() { | |||
runSelenese(orchestrator, "/uiExtension/UiExtensionsTest/page-decoration.html"); | |||
} | |||
@@ -56,6 +58,7 @@ public class UiExtensionsTest { | |||
* SONAR-4173 | |||
*/ | |||
@Test | |||
@Ignore("page extensions are not reimplemented yet") | |||
public void test_resource_configuration_extension() { | |||
runSelenese(orchestrator, "/uiExtension/UiExtensionsTest/resource-configuration-extension.html"); | |||
} |
@@ -183,6 +183,7 @@ public class LocalAuthenticationTest { | |||
} | |||
@Test | |||
@Ignore("signing up will be dropped: SONAR-7762") | |||
public void allow_users_to_sign_up() throws IOException { | |||
setServerProperty(ORCHESTRATOR, "sonar.allowUsersToSignUp", "true"); | |||
@@ -20,6 +20,7 @@ | |||
package it.user; | |||
import com.sonar.orchestrator.Orchestrator; | |||
import java.net.URLEncoder; | |||
import java.util.List; | |||
import javax.annotation.Nullable; | |||
import okhttp3.Response; | |||
@@ -30,6 +31,7 @@ import org.junit.ClassRule; | |||
import org.junit.Test; | |||
import util.user.UserRule; | |||
import static com.google.common.base.Charsets.UTF_8; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static util.ItUtils.call; | |||
@@ -126,7 +128,7 @@ public class SsoAuthenticationTest { | |||
Response response = doCall("invalid login $", null, null, null); | |||
assertThat(response.code()).isEqualTo(200); | |||
assertThat(response.body().string()).contains("You're not authorized to access this page. Please contact the administrator"); | |||
assertThat(response.request().url().toString()).contains("sessions/unauthorized"); | |||
List<String> logsLines = FileUtils.readLines(orchestrator.getServer().getWebLogs(), Charsets.UTF_8); | |||
assertThat(logsLines).doesNotContain("org.sonar.server.exceptions.BadRequestException: user.bad_login"); | |||
@@ -141,7 +143,7 @@ public class SsoAuthenticationTest { | |||
String expectedError = "You can't sign up because email 'tester@email.com' is already used by an existing user. This means that you probably already registered with another account"; | |||
assertThat(response.code()).isEqualTo(200); | |||
assertThat(response.body().string()).contains(expectedError); | |||
assertThat(response.request().url().toString()).contains(URLEncoder.encode(expectedError, UTF_8.name())); | |||
assertThat(FileUtils.readLines(orchestrator.getServer().getWebLogs(), Charsets.UTF_8)).doesNotContain(expectedError); | |||
} | |||
@@ -15,7 +15,12 @@ | |||
</tr> | |||
<tr> | |||
<td>open</td> | |||
<td>/updatecenter</td> | |||
<td>/sessions/login</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>waitForElementPresent</td> | |||
<td>name=commit</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
@@ -38,6 +43,11 @@ | |||
<td>css=.js-user-authenticated</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>open</td> | |||
<td>/updatecenter</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>waitForText</td> | |||
<td>content</td> |
@@ -23,11 +23,6 @@ | |||
<td>/</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>assertLocation</td> | |||
<td>*/sessions/new</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>waitForText</td> | |||
<td>content</td> | |||
@@ -63,12 +58,6 @@ | |||
<td>/sessions/logout</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>assertLocation</td> | |||
<td>*/sessions/new</td> | |||
<td></td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</body> |
@@ -17,11 +17,6 @@ | |||
<td>/settings</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>assertLocation</td> | |||
<td>*/sessions/new</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>waitForText</td> | |||
<td>content</td> |
@@ -20,8 +20,6 @@ module.exports = { | |||
'handlebars/runtime' | |||
], | |||
'sonar': './src/main/js/libs/sonar.js', | |||
'app': './src/main/js/app/index.js' | |||
}, | |||
output: { |
@@ -3,7 +3,7 @@ | |||
<head> | |||
<meta http-equiv="content-type" content="text/html; charset=UTF-8" charset="UTF-8"/> | |||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |||
<link href="%WEB_CONTEXT%/favicon.ico" rel="shortcut icon" type="image/x-icon"> | |||
<link href="%WEB_CONTEXT%/images/favicon.ico" rel="shortcut icon" type="image/x-icon"> | |||
<% for (var css in htmlWebpackPlugin.files.css) { %> | |||
<link href="%WEB_CONTEXT%/<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet"> | |||
<% } %> |
@@ -36,3 +36,10 @@ export const login = (login, password) => ( | |||
.submit() | |||
.then(basicCheckStatus) | |||
); | |||
export const logout = () => ( | |||
request('/api/authentication/logout') | |||
.setMethod('POST') | |||
.submit() | |||
.then(basicCheckStatus) | |||
); |
@@ -93,3 +93,8 @@ export function getServerId () { | |||
export function generateServerId (organization, ip) { | |||
return postJSON('/api/server_id/generate', { organization, ip }); | |||
} | |||
// TODO replace with /api/settings | |||
export const getSettingValue = key => ( | |||
getJSON(`/api/properties/${key}`).then(r => r[0] ? r[0].value : null) | |||
); |
@@ -30,7 +30,7 @@ export function getSystemInfo () { | |||
return getJSON(url); | |||
} | |||
export function getStatus () { | |||
export function getSystemStatus () { | |||
const url = '/api/system/status'; | |||
return getJSON(url); | |||
} | |||
@@ -44,7 +44,7 @@ const POLLING_INTERVAL = 2000; | |||
function pollStatus (cb) { | |||
setTimeout(() => { | |||
getStatus() | |||
getSystemStatus() | |||
.then(r => { | |||
if (r.status === 'UP') { | |||
cb(); |
@@ -18,30 +18,36 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { Link } from 'react-router'; | |||
import OAuthProvider from './OAuthProvider'; | |||
import IconLock from './IconLock'; | |||
import { connect } from 'react-redux'; | |||
import SettingsNav from './nav/settings/SettingsNav'; | |||
import { getCurrentUser } from '../store/rootReducer'; | |||
import { isUserAdmin } from '../../helpers/users'; | |||
export default class LoginSection extends React.Component { | |||
render () { | |||
const { authProviders } = window.sonarqube; | |||
class AdminContainer extends React.Component { | |||
componentDidMount () { | |||
if (!isUserAdmin(this.props.currentUser)) { | |||
// workaround cyclic dependencies | |||
const handleRequiredAuthorization = require('../utils/handleRequiredAuthorization').default; | |||
handleRequiredAuthorization(); | |||
} | |||
} | |||
const loginWithSonarQubeLabel = authProviders.length ? 'Log in with SonarQube' : 'Log in'; | |||
render () { | |||
if (!isUserAdmin(this.props.currentUser)) { | |||
return null; | |||
} | |||
return ( | |||
<div id="about-login"> | |||
<div className="about-page-auth-providers big-spacer-top"> | |||
{authProviders.map(provider => ( | |||
<OAuthProvider key={provider.key} provider={provider}/> | |||
))} | |||
<Link to={{ pathname: '/about', query: { login: null } }} | |||
className="oauth-provider oauth-provider-sonarqube"> | |||
<IconLock/> | |||
<span>{loginWithSonarQubeLabel}</span> | |||
</Link> | |||
</div> | |||
<div> | |||
<SettingsNav/> | |||
{this.props.children} | |||
</div> | |||
); | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
currentUser: getCurrentUser(state) | |||
}); | |||
export default connect(mapStateToProps)(AdminContainer); |
@@ -17,27 +17,57 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import GlobalLoading from './GlobalLoading'; | |||
import { fetchCurrentUser } from '../store/users/actions'; | |||
import { fetchLanguages } from '../store/rootActions'; | |||
import { fetchLanguages, fetchAppState } from '../store/rootActions'; | |||
class App extends React.Component { | |||
mounted: bool; | |||
static propTypes = { | |||
fetchCurrentUser: React.PropTypes.func.isRequired | |||
fetchAppState: React.PropTypes.func.isRequired, | |||
fetchCurrentUser: React.PropTypes.func.isRequired, | |||
fetchLanguages: React.PropTypes.func.isRequired, | |||
children: React.PropTypes.element.isRequired | |||
}; | |||
state = { | |||
loading: true | |||
}; | |||
finishLoading = () => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
}; | |||
componentDidMount () { | |||
this.props.fetchCurrentUser(); | |||
this.props.fetchLanguages(); | |||
this.mounted = true; | |||
this.props.fetchCurrentUser() | |||
.then(this.props.fetchAppState) | |||
.then(this.finishLoading) | |||
.then(this.props.fetchLanguages) | |||
.catch(this.finishLoading); | |||
} | |||
componentWillUnmount () { | |||
this.mounted = false; | |||
} | |||
render () { | |||
if (this.state.loading) { | |||
return <GlobalLoading/>; | |||
} | |||
return this.props.children; | |||
} | |||
} | |||
export default connect( | |||
() => ({}), | |||
{ fetchCurrentUser, fetchLanguages } | |||
null, | |||
{ fetchAppState, fetchCurrentUser, fetchLanguages } | |||
)(App); |
@@ -17,24 +17,23 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import classNames from 'classnames'; | |||
export default { | |||
activeLink(url) { | |||
return window.location.pathname.indexOf(window.baseUrl + url) === 0 ? 'active' : null; | |||
}, | |||
renderLink(url, title, highlightUrl = url) { | |||
const fullUrl = window.baseUrl + url; | |||
const isActive = typeof highlightUrl === 'string' ? | |||
window.location.pathname.indexOf(window.baseUrl + highlightUrl) === 0 : | |||
highlightUrl(fullUrl); | |||
import GlobalNav from './nav/global/GlobalNav'; | |||
import GlobalFooter from './GlobalFooter'; | |||
import GlobalMessagesContainer from './GlobalMessagesContainer'; | |||
export default class GlobalContainer extends React.Component { | |||
render () { | |||
return ( | |||
<li key={url} className={classNames({ 'active': isActive })}> | |||
<a href={fullUrl}>{title}</a> | |||
</li> | |||
<div className="global-container"> | |||
<div className="page-wrapper page-wrapper-global" id="container"> | |||
<GlobalNav/> | |||
<GlobalMessagesContainer/> | |||
{this.props.children} | |||
</div> | |||
<GlobalFooter/> | |||
</div> | |||
); | |||
} | |||
}; | |||
} |
@@ -0,0 +1,81 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { getAppState } from '../store/rootReducer'; | |||
class GlobalFooter extends React.Component { | |||
render () { | |||
const { sonarqubeVersion, productionDatabase } = this.props; | |||
return ( | |||
<div id="footer" className="page-footer page-container"> | |||
{!productionDatabase && ( | |||
<div className="alert alert-danger"> | |||
<p className="big" id="evaluation_warning"> | |||
Embedded database should be used for evaluation purpose only | |||
</p> | |||
<p> | |||
The embedded database will not scale, it will not support upgrading to newer versions of SonarQube, | |||
and there is no support for migrating your data out of it into a different database engine. | |||
</p> | |||
</div> | |||
)} | |||
<div> | |||
This application is based on | |||
{' '} | |||
<a href="http://www.sonarqube.org/" title="SonarQube™">SonarQube™</a> | |||
{' '} | |||
but is <strong>not</strong> an official version provided by | |||
{' '} | |||
<a href="http://www.sonarsource.com" title="SonarSource SA">SonarSource SA</a>. | |||
</div> | |||
<div> | |||
Version {sonarqubeVersion} | |||
{' - '} | |||
<a href="http://www.gnu.org/licenses/lgpl-3.0.txt">LGPL v3</a> | |||
{' - '} | |||
<a href="http://www.sonarqube.org">Community</a> | |||
{' - '} | |||
<a href="http://www.sonarqube.org/documentation">Documentation</a> | |||
{' - '} | |||
<a href="http://www.sonarqube.org/support">Get Support</a> | |||
{' - '} | |||
<a href="http://redirect.sonarsource.com/doc/plugin-library.html">Plugins</a> | |||
{' - '} | |||
<a href={window.baseUrl + '/web_api'}>Web API</a> | |||
{' - '} | |||
<a href={window.baseUrl + '/about'}>About</a> | |||
</div> | |||
</div> | |||
); | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
sonarqubeVersion: getAppState(state).version, | |||
productionDatabase: getAppState(state).productionDatabase | |||
}); | |||
export default connect(mapStateToProps)(GlobalFooter); |
@@ -0,0 +1,19 @@ | |||
.global-loading { | |||
width: 300px; | |||
margin: 200px auto 0; | |||
white-space: nowrap; | |||
} | |||
.global-loading-spinner { | |||
vertical-align: middle; | |||
width: 80px; | |||
height: 80px; | |||
} | |||
.global-loading-text { | |||
display: inline-block; | |||
vertical-align: middle; | |||
margin-left: 30px; | |||
font-size: 36px; | |||
font-weight: 300; | |||
} |
@@ -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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import './GlobalLoading.css'; | |||
export default class GlobalLoading extends React.Component { | |||
render () { | |||
return ( | |||
<div className="global-loading"> | |||
<i className="spinner global-loading-spinner"/> | |||
<span className="global-loading-text">Loading...</span> | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,49 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { withRouter } from 'react-router'; | |||
import { connect } from 'react-redux'; | |||
import { getCurrentUser } from '../store/rootReducer'; | |||
class Landing extends React.Component { | |||
static propTypes = { | |||
currentUser: React.PropTypes.oneOfType([React.PropTypes.bool, React.PropTypes.object]).isRequired | |||
}; | |||
componentDidMount () { | |||
const { currentUser, router } = this.props; | |||
if (currentUser.isLoggedIn) { | |||
router.replace('/projects/favorite'); | |||
} else { | |||
router.replace('/about'); | |||
} | |||
} | |||
render () { | |||
return null; | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
currentUser: getCurrentUser(state) | |||
}); | |||
export default connect(mapStateToProps)(withRouter(Landing)); |
@@ -0,0 +1,49 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { requestMessages } from '../../helpers/l10n'; | |||
export default class LocalizationContainer extends React.Component { | |||
mounted: bool; | |||
state = { | |||
loading: true | |||
}; | |||
finishLoading = () => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
}; | |||
componentDidMount () { | |||
this.mounted = true; | |||
requestMessages().then(this.finishLoading, this.finishLoading); | |||
} | |||
componentWillUnmount () { | |||
this.mounted = false; | |||
} | |||
render () { | |||
return this.state.loading ? null : this.props.children; | |||
} | |||
} |
@@ -0,0 +1,52 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { getSystemStatus } from '../../api/system'; | |||
export default class MigrationContainer extends React.Component { | |||
static propTypes = { | |||
children: React.PropTypes.element.isRequired | |||
}; | |||
state = { | |||
loading: true | |||
}; | |||
componentDidMount () { | |||
getSystemStatus().then(r => { | |||
if (r.status === 'UP') { | |||
this.setState({ loading: false }); | |||
} else { | |||
// workaround cyclic dependencies | |||
const handleRequiredMigration = require('../utils/handleRequiredMigration').default; | |||
handleRequiredMigration(); | |||
} | |||
}); | |||
} | |||
render () { | |||
if (this.state.loading) { | |||
return null; | |||
} | |||
return this.props.children; | |||
} | |||
} |
@@ -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 SimpleContainer from './SimpleContainer'; | |||
export default class NotFound extends React.Component { | |||
render () { | |||
return ( | |||
<SimpleContainer> | |||
<h2 className="big-spacer-bottom">The page you were looking for does not exist.</h2> | |||
<p className="spacer-bottom">You may have mistyped the address or the page may have moved.</p> | |||
<p><a href={window.baseUrl + '/'}>Go back to the homepage</a></p> | |||
</SimpleContainer> | |||
); | |||
} | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import ComponentNav from './nav/component/ComponentNav'; | |||
import { fetchProject } from '../store/rootActions'; | |||
import { getComponent } from '../store/rootReducer'; | |||
class ProjectContainer extends React.Component { | |||
static propTypes = { | |||
project: React.PropTypes.object, | |||
fetchProject: React.PropTypes.func.isRequired | |||
}; | |||
componentDidMount () { | |||
this.props.fetchProject(); | |||
} | |||
render () { | |||
if (!this.props.project) { | |||
return null; | |||
} | |||
const isFile = ['FIL', 'UTS'].includes(this.props.project.qualifier); | |||
const configuration = this.props.project.configuration || {}; | |||
return ( | |||
<div> | |||
{!isFile && ( | |||
<ComponentNav component={this.props.project} conf={configuration}/> | |||
)} | |||
{this.props.children} | |||
</div> | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state, ownProps) => ({ | |||
project: getComponent(state, ownProps.location.query.id) | |||
}); | |||
const mapDispatchToProps = (dispatch, ownProps) => ({ | |||
fetchProject: () => dispatch(fetchProject(ownProps.location.query.id)) | |||
}); | |||
export default connect(mapStateToProps, mapDispatchToProps)(ProjectContainer); |
@@ -0,0 +1,55 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import GlobalFooter from './GlobalFooter'; | |||
export default class SimpleContainer extends React.Component { | |||
static propTypes = { | |||
children: React.PropTypes.element.isRequired | |||
}; | |||
componentDidMount () { | |||
document.querySelector('html').classList.add('dashboard-page'); | |||
} | |||
componentWillUnmount () { | |||
document.querySelector('html').classList.remove('dashboard-page'); | |||
} | |||
render () { | |||
return ( | |||
<div className="global-container"> | |||
<div className="page-wrapper page-wrapper-global" id="container"> | |||
<nav className="navbar navbar-global page-container" id="global-navigation"> | |||
<div className="navbar-header"></div> | |||
</nav> | |||
<div id="bd" className="page-wrapper-simple"> | |||
<div id="nonav" className="page-simple"> | |||
{this.props.children} | |||
</div> | |||
</div> | |||
</div> | |||
<GlobalFooter/> | |||
</div> | |||
); | |||
} | |||
} |
@@ -1,91 +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 ReactDOM from 'react-dom'; | |||
import GlobalNav from './global/global-nav'; | |||
import ComponentNav from './component/component-nav'; | |||
import SettingsNav from './settings/settings-nav'; | |||
import { getGlobalNavigation, getComponentNavigation, getSettingsNavigation } from '../../../api/nav'; | |||
export default class App { | |||
start () { | |||
const options = window.sonarqube; | |||
require('../../../components/workspace/main'); | |||
return new Promise(resolve => { | |||
const response = {}; | |||
const requests = []; | |||
requests.push( | |||
App.renderGlobalNav(options).then(r => response.global = r) | |||
); | |||
if (options.space === 'component') { | |||
requests.push( | |||
App.renderComponentNav(options).then(r => response.component = r) | |||
); | |||
} else if (options.space === 'settings') { | |||
requests.push( | |||
App.renderSettingsNav(options).then(r => response.settings = r) | |||
); | |||
} | |||
Promise.all(requests).then(() => resolve(response)); | |||
}); | |||
} | |||
static renderGlobalNav (options) { | |||
return getGlobalNavigation().then(r => { | |||
const el = document.getElementById('global-navigation'); | |||
if (el) { | |||
ReactDOM.render(<GlobalNav {...options} {...r}/>, el); | |||
} | |||
return r; | |||
}); | |||
} | |||
static renderComponentNav (options) { | |||
return getComponentNavigation(options.componentKey).then(component => { | |||
const el = document.getElementById('context-navigation'); | |||
const nextComponent = { | |||
...component, | |||
qualifier: _.last(component.breadcrumbs).qualifier | |||
}; | |||
if (el) { | |||
ReactDOM.render(<ComponentNav component={nextComponent} conf={component.configuration || {}}/>, el); | |||
} | |||
return component; | |||
}); | |||
} | |||
static renderSettingsNav (options) { | |||
return getSettingsNavigation().then(r => { | |||
const el = document.getElementById('context-navigation'); | |||
const opts = _.extend(r, options); | |||
if (el) { | |||
ReactDOM.render(<SettingsNav {...opts}/>, el); | |||
} | |||
return r; | |||
}); | |||
} | |||
} |
@@ -0,0 +1,16 @@ | |||
.navbar-context { | |||
position: static; | |||
padding: 0; | |||
height: 65px; | |||
} | |||
.navbar-context-inner { | |||
position: fixed; | |||
z-index: 420; | |||
left: 0; | |||
right: 0; | |||
height: 65px; | |||
padding-top: 5px; | |||
box-sizing: border-box; | |||
background-color: #f3f3f3; | |||
} |
@@ -23,11 +23,12 @@ import React from 'react'; | |||
import ReactDOM from 'react-dom'; | |||
import { STATUSES } from '../../../../apps/background-tasks/constants'; | |||
import { getTasksForComponent } from '../../../../api/ce'; | |||
import ComponentNavFavorite from './component-nav-favorite'; | |||
import ComponentNavBreadcrumbs from './component-nav-breadcrumbs'; | |||
import ComponentNavMeta from './component-nav-meta'; | |||
import ComponentNavMenu from './component-nav-menu'; | |||
import ComponentNavFavorite from './ComponentNavFavorite'; | |||
import ComponentNavBreadcrumbs from './ComponentNavBreadcrumbs'; | |||
import ComponentNavMeta from './ComponentNavMeta'; | |||
import ComponentNavMenu from './ComponentNavMenu'; | |||
import RecentHistory from './RecentHistory'; | |||
import './ComponentNav.css'; | |||
export default React.createClass({ | |||
componentDidMount() { | |||
@@ -63,25 +64,29 @@ export default React.createClass({ | |||
render() { | |||
return ( | |||
<div className="container"> | |||
<ComponentNavFavorite | |||
component={this.props.component.key} | |||
favorite={this.props.component.isFavorite} | |||
canBeFavorite={this.props.component.canBeFavorite}/> | |||
<nav className="navbar navbar-context page-container" id="context-navigation"> | |||
<div className="navbar-context-inner"> | |||
<div className="container"> | |||
<ComponentNavFavorite | |||
component={this.props.component.key} | |||
favorite={this.props.component.isFavorite} | |||
canBeFavorite={this.props.component.canBeFavorite}/> | |||
<ComponentNavBreadcrumbs | |||
breadcrumbs={this.props.component.breadcrumbs}/> | |||
<ComponentNavBreadcrumbs | |||
breadcrumbs={this.props.component.breadcrumbs}/> | |||
<ComponentNavMeta | |||
{...this.props} | |||
{...this.state} | |||
version={this.props.component.version} | |||
snapshotDate={this.props.component.snapshotDate}/> | |||
<ComponentNavMeta | |||
{...this.props} | |||
{...this.state} | |||
version={this.props.component.version} | |||
snapshotDate={this.props.component.snapshotDate}/> | |||
<ComponentNavMenu | |||
component={this.props.component} | |||
conf={this.props.conf}/> | |||
</div> | |||
<ComponentNavMenu | |||
component={this.props.component} | |||
conf={this.props.conf}/> | |||
</div> | |||
</div> | |||
</nav> | |||
); | |||
} | |||
}); |
@@ -20,11 +20,16 @@ | |||
import React from 'react'; | |||
import QualifierIcon from '../../../../components/shared/qualifier-icon'; | |||
export default React.createClass({ | |||
render() { | |||
export default class ComponentNavBreadcrumbs extends React.Component { | |||
static propTypes = { | |||
breadcrumbs: React.PropTypes.array | |||
}; | |||
render () { | |||
if (!this.props.breadcrumbs) { | |||
return null; | |||
} | |||
const items = this.props.breadcrumbs.map((item, index) => { | |||
const url = `${window.baseUrl}/dashboard/index?id=${encodeURIComponent(item.key)}`; | |||
return ( | |||
@@ -35,8 +40,9 @@ export default React.createClass({ | |||
</li> | |||
); | |||
}); | |||
return ( | |||
<ul className="nav navbar-nav nav-crumbs">{items}</ul> | |||
); | |||
} | |||
}); | |||
} |
@@ -20,11 +20,16 @@ | |||
import React from 'react'; | |||
import Favorite from '../../../../components/controls/Favorite'; | |||
export default React.createClass({ | |||
render() { | |||
export default class ComponentNavFavorite extends React.Component { | |||
static propTypes = { | |||
canBeFavorite: React.PropTypes.bool.isRequired | |||
}; | |||
render () { | |||
if (!this.props.canBeFavorite) { | |||
return null; | |||
} | |||
return ( | |||
<div className="navbar-context-favorite"> | |||
<Favorite | |||
@@ -33,4 +38,4 @@ export default React.createClass({ | |||
</div> | |||
); | |||
} | |||
}); | |||
} |
@@ -17,10 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import qs from 'querystring'; | |||
import classNames from 'classnames'; | |||
import React from 'react'; | |||
import LinksMixin from '../links-mixin'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { getComponentUrl } from '../../../../helpers/urls'; | |||
@@ -37,38 +35,44 @@ const SETTINGS_URLS = [ | |||
'/project/deletion' | |||
]; | |||
export default React.createClass({ | |||
mixins: [LinksMixin], | |||
export default class ComponentNavMenu extends React.Component { | |||
static propTypes = { | |||
component: React.PropTypes.object.isRequired, | |||
conf: React.PropTypes.object.isRequired | |||
}; | |||
isDeveloper() { | |||
isDeveloper () { | |||
return this.props.component.qualifier === 'DEV'; | |||
}, | |||
} | |||
isView() { | |||
isView () { | |||
const { qualifier } = this.props.component; | |||
return qualifier === 'VW' || qualifier === 'SVW'; | |||
}, | |||
periodParameter() { | |||
const params = qs.parse(window.location.search.substr(1)); | |||
return params.period ? `&period=${params.period}` : ''; | |||
}, | |||
getPeriod() { | |||
const params = qs.parse(window.location.search.substr(1)); | |||
return params.period; | |||
}, | |||
} | |||
isFixedDashboardActive() { | |||
isFixedDashboardActive () { | |||
const path = window.location.pathname; | |||
return path.indexOf(window.baseUrl + '/dashboard') === 0 || path.indexOf(window.baseUrl + '/governance') === 0; | |||
}, | |||
} | |||
shouldShowAdministration() { | |||
shouldShowAdministration () { | |||
return Object.keys(this.props.conf).some(key => this.props.conf[key]); | |||
}, | |||
} | |||
renderDashboardLink() { | |||
renderLink (url, title, highlightUrl = url) { | |||
const fullUrl = window.baseUrl + url; | |||
const isActive = typeof highlightUrl === 'string' ? | |||
window.location.pathname.indexOf(window.baseUrl + highlightUrl) === 0 : | |||
highlightUrl(fullUrl); | |||
return ( | |||
<li key={url} className={classNames({ 'active': isActive })}> | |||
<a href={fullUrl}>{title}</a> | |||
</li> | |||
); | |||
} | |||
renderDashboardLink () { | |||
const url = getComponentUrl(this.props.component.key); | |||
const name = <i className="icon-home"/>; | |||
const className = classNames({ active: this.isFixedDashboardActive() }); | |||
@@ -77,9 +81,9 @@ export default React.createClass({ | |||
<a href={url}>{name}</a> | |||
</li> | |||
); | |||
}, | |||
} | |||
renderCodeLink() { | |||
renderCodeLink () { | |||
if (this.isDeveloper()) { | |||
return null; | |||
} | |||
@@ -87,19 +91,19 @@ export default React.createClass({ | |||
const url = `/code/?id=${encodeURIComponent(this.props.component.key)}`; | |||
const header = this.isView() ? translate('view_projects.page') : translate('code.page'); | |||
return this.renderLink(url, header, '/code'); | |||
}, | |||
} | |||
renderComponentIssuesLink() { | |||
renderComponentIssuesLink () { | |||
const url = `/component_issues?id=${encodeURIComponent(this.props.component.key)}`; | |||
return this.renderLink(url, translate('issues.page'), '/component_issues'); | |||
}, | |||
} | |||
renderComponentMeasuresLink() { | |||
renderComponentMeasuresLink () { | |||
const url = `/component_measures/?id=${encodeURIComponent(this.props.component.key)}`; | |||
return this.renderLink(url, translate('layout.measures'), '/component_measures'); | |||
}, | |||
} | |||
renderAdministration() { | |||
renderAdministration () { | |||
if (!this.shouldShowAdministration()) { | |||
return null; | |||
} | |||
@@ -126,81 +130,81 @@ export default React.createClass({ | |||
</ul> | |||
</li> | |||
); | |||
}, | |||
} | |||
renderSettingsLink() { | |||
renderSettingsLink () { | |||
if (!this.props.conf.showSettings) { | |||
return null; | |||
} | |||
const url = `/project/settings?id=${encodeURIComponent(this.props.component.key)}`; | |||
return this.renderLink(url, translate('project_settings.page'), '/project/settings'); | |||
}, | |||
} | |||
renderProfilesLink() { | |||
renderProfilesLink () { | |||
if (!this.props.conf.showQualityProfiles) { | |||
return null; | |||
} | |||
const url = `/project/quality_profiles?id=${encodeURIComponent(this.props.component.key)}`; | |||
return this.renderLink(url, translate('project_quality_profiles.page'), '/project/quality_profiles'); | |||
}, | |||
} | |||
renderQualityGateLink() { | |||
renderQualityGateLink () { | |||
if (!this.props.conf.showQualityGates) { | |||
return null; | |||
} | |||
const url = `/project/quality_gate?id=${encodeURIComponent(this.props.component.key)}`; | |||
return this.renderLink(url, translate('project_quality_gate.page'), '/project/quality_gate'); | |||
}, | |||
} | |||
renderCustomMeasuresLink() { | |||
renderCustomMeasuresLink () { | |||
if (!this.props.conf.showManualMeasures) { | |||
return null; | |||
} | |||
const url = `/custom_measures?id=${encodeURIComponent(this.props.component.key)}`; | |||
return this.renderLink(url, translate('custom_measures.page'), '/custom_measures'); | |||
}, | |||
} | |||
renderLinksLink() { | |||
renderLinksLink () { | |||
if (!this.props.conf.showLinks) { | |||
return null; | |||
} | |||
const url = `/project/links?id=${encodeURIComponent(this.props.component.key)}`; | |||
return this.renderLink(url, translate('project_links.page'), '/project/links'); | |||
}, | |||
} | |||
renderPermissionsLink() { | |||
renderPermissionsLink () { | |||
if (!this.props.conf.showPermissions) { | |||
return null; | |||
} | |||
const url = `/project_roles?id=${encodeURIComponent(this.props.component.key)}`; | |||
return this.renderLink(url, translate('permissions.page'), '/project_roles'); | |||
}, | |||
} | |||
renderHistoryLink() { | |||
renderHistoryLink () { | |||
if (!this.props.conf.showHistory) { | |||
return null; | |||
} | |||
const url = `/project/history?id=${encodeURIComponent(this.props.component.key)}`; | |||
return this.renderLink(url, translate('project_history.page'), '/project/history'); | |||
}, | |||
} | |||
renderBackgroundTasksLink() { | |||
renderBackgroundTasksLink () { | |||
if (!this.props.conf.showBackgroundTasks) { | |||
return null; | |||
} | |||
const url = `/project/background_tasks?id=${encodeURIComponent(this.props.component.key)}`; | |||
return this.renderLink(url, translate('background_tasks.page'), '/project/background_tasks'); | |||
}, | |||
} | |||
renderUpdateKeyLink() { | |||
renderUpdateKeyLink () { | |||
if (!this.props.conf.showUpdateKey) { | |||
return null; | |||
} | |||
const url = `/project/key?id=${encodeURIComponent(this.props.component.key)}`; | |||
return this.renderLink(url, translate('update_key.page'), '/project/key'); | |||
}, | |||
} | |||
renderDeletionLink() { | |||
renderDeletionLink () { | |||
const { qualifier } = this.props.component; | |||
if (qualifier !== 'TRK' && qualifier !== 'VW') { | |||
@@ -209,14 +213,14 @@ export default React.createClass({ | |||
const url = `/project/deletion?id=${encodeURIComponent(this.props.component.key)}`; | |||
return this.renderLink(url, translate('deletion.page'), '/project/deletion'); | |||
}, | |||
} | |||
renderExtensions() { | |||
renderExtensions () { | |||
const extensions = this.props.conf.extensions || []; | |||
return extensions.map(e => this.renderLink(e.url, e.name, e.url)); | |||
}, | |||
} | |||
renderTools() { | |||
renderTools () { | |||
const extensions = this.props.component.extensions || []; | |||
const withoutGovernance = extensions.filter(ext => ext.name !== 'Governance'); | |||
const tools = withoutGovernance | |||
@@ -237,9 +241,9 @@ export default React.createClass({ | |||
</ul> | |||
</li> | |||
); | |||
}, | |||
} | |||
render() { | |||
render () { | |||
return ( | |||
<ul className="nav navbar-nav nav-tabs"> | |||
{this.renderDashboardLink()} | |||
@@ -251,4 +255,4 @@ export default React.createClass({ | |||
</ul> | |||
); | |||
} | |||
}); | |||
} |
@@ -19,13 +19,11 @@ | |||
*/ | |||
import React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import ComponentNavBreadcrumbs from '../component/component-nav-breadcrumbs'; | |||
import ComponentNavBreadcrumbs from '../ComponentNavBreadcrumbs'; | |||
describe('ComponentNavBreadcrumbs', () => { | |||
it('should not render breadcrumbs with one element', function () { | |||
const breadcrumbs = [{ key: 'my-project', name: 'My Project', qualifier: 'TRK' }]; | |||
const result = shallow(<ComponentNavBreadcrumbs breadcrumbs={breadcrumbs}/>); | |||
expect(result.find('li').length).toBe(1); | |||
expect(result.find('a').length).toBe(1); | |||
}); | |||
it('should not render breadcrumbs with one element', function () { | |||
const breadcrumbs = [{ key: 'my-project', name: 'My Project', qualifier: 'TRK' }]; | |||
const result = shallow(<ComponentNavBreadcrumbs breadcrumbs={breadcrumbs}/>); | |||
expect(result.find('li').length).toBe(1); | |||
expect(result.find('a').length).toBe(1); | |||
}); |
@@ -18,22 +18,24 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import GlobalNavBranding from './global-nav-branding'; | |||
import GlobalNavMenu from './global-nav-menu'; | |||
import GlobalNavUser from './global-nav-user'; | |||
import GlobalNavSearch from './global-nav-search'; | |||
import ShortcutsHelpView from './shortcuts-help-view'; | |||
import { connect } from 'react-redux'; | |||
import GlobalNavBranding from './GlobalNavBranding'; | |||
import GlobalNavMenu from './GlobalNavMenu'; | |||
import GlobalNavUser from './GlobalNavUser'; | |||
import GlobalNavSearch from './GlobalNavSearch'; | |||
import ShortcutsHelpView from './ShortcutsHelpView'; | |||
import { getCurrentUser } from '../../../store/rootReducer'; | |||
export default React.createClass({ | |||
componentDidMount() { | |||
class GlobalNav extends React.Component { | |||
componentDidMount () { | |||
window.addEventListener('keypress', this.onKeyPress); | |||
}, | |||
} | |||
componentWillUnmount() { | |||
componentWillUnmount () { | |||
window.removeEventListener('keypress', this.onKeyPress); | |||
}, | |||
} | |||
onKeyPress(e) { | |||
onKeyPress = e => { | |||
const tagName = e.target.tagName; | |||
const code = e.keyCode || e.which; | |||
const isInput = tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA'; | |||
@@ -42,32 +44,40 @@ export default React.createClass({ | |||
if (!isInput && !isModalOpen && isTriggerKey) { | |||
this.openHelp(); | |||
} | |||
}, | |||
}; | |||
openHelp(e) { | |||
openHelp = e => { | |||
if (e) { | |||
e.preventDefault(); | |||
} | |||
new ShortcutsHelpView().render(); | |||
}, | |||
}; | |||
render() { | |||
render () { | |||
return ( | |||
<div className="container"> | |||
<GlobalNavBranding {...this.props}/> | |||
<nav className="navbar navbar-global page-container" id="global-navigation"> | |||
<div className="container"> | |||
<GlobalNavBranding/> | |||
<GlobalNavMenu {...this.props}/> | |||
<GlobalNavMenu {...this.props}/> | |||
<ul className="nav navbar-nav navbar-right"> | |||
<GlobalNavUser {...this.props}/> | |||
<GlobalNavSearch {...this.props}/> | |||
<li> | |||
<a onClick={this.openHelp} href="#"> | |||
<i className="icon-help navbar-icon"/> | |||
</a> | |||
</li> | |||
</ul> | |||
</div> | |||
<ul className="nav navbar-nav navbar-right"> | |||
<GlobalNavUser {...this.props}/> | |||
<GlobalNavSearch {...this.props}/> | |||
<li> | |||
<a onClick={this.openHelp} href="#"> | |||
<i className="icon-help navbar-icon"/> | |||
</a> | |||
</li> | |||
</ul> | |||
</div> | |||
</nav> | |||
); | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
currentUser: getCurrentUser(state) | |||
}); | |||
export default connect(mapStateToProps)(GlobalNav); |
@@ -18,29 +18,42 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { getSettingValue, getCurrentUser } from '../../../store/rootReducer'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
export default React.createClass({ | |||
renderLogo() { | |||
const url = this.props.logoUrl || `${window.baseUrl}/images/logo.svg`; | |||
const width = this.props.logoWidth || 100; | |||
class GlobalNavBranding extends React.Component { | |||
static propTypes = { | |||
customLogoUrl: React.PropTypes.string, | |||
customLogoWidth: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]) | |||
}; | |||
renderLogo () { | |||
const url = this.props.customLogoUrl || `${window.baseUrl}/images/logo.svg`; | |||
const width = this.props.customLogoWidth || 100; | |||
const height = 30; | |||
const title = translate('layout.sonar.slogan'); | |||
return <img src={url} | |||
width={width} | |||
height={height} | |||
alt={title} | |||
title={title}/>; | |||
}, | |||
return ( | |||
<img src={url} width={width} height={height} alt={title} title={title}/> | |||
); | |||
} | |||
render() { | |||
const homeController = window.SS.user ? '/projects/favorite' : '/about'; | |||
render () { | |||
const homeController = this.props.currentUser.isLoggedIn ? '/projects/favorite' : '/about'; | |||
const homeUrl = window.baseUrl + homeController; | |||
const homeLinkClassName = 'navbar-brand' + (this.props.logoUrl ? ' navbar-brand-custom' : ''); | |||
const homeLinkClassName = 'navbar-brand' + (this.props.customLogoUrl ? ' navbar-brand-custom' : ''); | |||
return ( | |||
<div className="navbar-header"> | |||
<a className={homeLinkClassName} href={homeUrl}>{this.renderLogo()}</a> | |||
</div> | |||
); | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
currentUser: getCurrentUser(state), | |||
customLogoUrl: (getSettingValue(state, 'sonar.lf.logoUrl') || {}).value, | |||
customLogoWidth: (getSettingValue(state, 'sonar.lf.logoWidthPx') || {}).value | |||
}); | |||
export default connect(mapStateToProps)(GlobalNavBranding); |
@@ -18,36 +18,42 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import DashboardNameMixin from '../dashboard-name-mixin'; | |||
import LinksMixin from '../links-mixin'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { isUserAdmin } from '../../../../helpers/users'; | |||
export default React.createClass({ | |||
mixins: [DashboardNameMixin, LinksMixin], | |||
export default class GlobalNavMenu extends React.Component { | |||
static propTypes = { | |||
currentUser: React.PropTypes.object.isRequired | |||
}; | |||
getDefaultProps () { | |||
return { globalDashboards: [], globalPages: [] }; | |||
}, | |||
static defaultProps = { | |||
globalDashboards: [], | |||
globalPages: [] | |||
}; | |||
activeLink (url) { | |||
return window.location.pathname.indexOf(window.baseUrl + url) === 0 ? 'active' : null; | |||
} | |||
renderProjects () { | |||
const controller = window.SS.user ? '/projects/favorite' : '/projects'; | |||
const controller = this.props.currentUser.isLoggedIn ? '/projects/favorite' : '/projects'; | |||
const url = window.baseUrl + controller; | |||
return ( | |||
<li className={this.activeLink('/projects')}> | |||
<a href={url}>{translate('projects.page')}</a> | |||
</li> | |||
); | |||
}, | |||
} | |||
renderIssuesLink () { | |||
const query = window.SS.user ? '#resolved=false|assigned_to_me=true' : '#resolved=false'; | |||
const query = this.props.currentUser.isLoggedIn ? '#resolved=false|assigned_to_me=true' : '#resolved=false'; | |||
const url = window.baseUrl + '/issues' + query; | |||
return ( | |||
<li className={this.activeLink('/issues')}> | |||
<a href={url}>{translate('issues.page')}</a> | |||
</li> | |||
); | |||
}, | |||
} | |||
renderRulesLink () { | |||
const url = window.baseUrl + '/coding_rules'; | |||
@@ -56,16 +62,16 @@ export default React.createClass({ | |||
<a href={url}>{translate('coding_rules.page')}</a> | |||
</li> | |||
); | |||
}, | |||
} | |||
renderProfilesLink() { | |||
renderProfilesLink () { | |||
const url = window.baseUrl + '/profiles'; | |||
return ( | |||
<li className={this.activeLink('/profiles')}> | |||
<a href={url}>{translate('quality_profiles.page')}</a> | |||
</li> | |||
); | |||
}, | |||
} | |||
renderQualityGatesLink () { | |||
const url = window.baseUrl + '/quality_gates'; | |||
@@ -74,10 +80,10 @@ export default React.createClass({ | |||
<a href={url}>{translate('quality_gates.page')}</a> | |||
</li> | |||
); | |||
}, | |||
} | |||
renderAdministrationLink () { | |||
if (!window.SS.isUserAdmin) { | |||
if (!isUserAdmin(this.props.currentUser)) { | |||
return null; | |||
} | |||
const url = window.baseUrl + '/settings'; | |||
@@ -86,7 +92,7 @@ export default React.createClass({ | |||
<a className="navbar-admin-link" href={url}>{translate('layout.settings')}</a> | |||
</li> | |||
); | |||
}, | |||
} | |||
renderGlobalPageLink (globalPage, index) { | |||
const url = window.baseUrl + globalPage.url; | |||
@@ -95,13 +101,13 @@ export default React.createClass({ | |||
<a href={url}>{globalPage.name}</a> | |||
</li> | |||
); | |||
}, | |||
} | |||
renderMore () { | |||
if (this.props.globalPages.length === 0) { | |||
return null; | |||
} | |||
const globalPages = this.props.globalPages.map(this.renderGlobalPageLink); | |||
const globalPages = this.props.globalPages.map((p, i) => this.renderGlobalPageLink(p, i)); | |||
return ( | |||
<li className="dropdown"> | |||
<a className="dropdown-toggle" data-toggle="dropdown" href="#"> | |||
@@ -113,7 +119,7 @@ export default React.createClass({ | |||
</ul> | |||
</li> | |||
); | |||
}, | |||
} | |||
render () { | |||
return ( | |||
@@ -128,4 +134,4 @@ export default React.createClass({ | |||
</ul> | |||
); | |||
} | |||
}); | |||
} |
@@ -19,7 +19,9 @@ | |||
*/ | |||
import Backbone from 'backbone'; | |||
import React from 'react'; | |||
import SearchView from './search-view'; | |||
import { connect } from 'react-redux'; | |||
import SearchView from './SearchView'; | |||
import { getCurrentUser } from '../../../store/rootReducer'; | |||
function contains (root, node) { | |||
while (node) { | |||
@@ -31,12 +33,10 @@ function contains (root, node) { | |||
return false; | |||
} | |||
export default React.createClass({ | |||
getInitialState() { | |||
return { open: false }; | |||
}, | |||
class GlobalNavSearch extends React.Component { | |||
state = { open: false }; | |||
componentDidMount() { | |||
componentDidMount () { | |||
key('s', () => { | |||
const isModalOpen = document.querySelector('html').classList.contains('modal-open'); | |||
if (!isModalOpen) { | |||
@@ -44,55 +44,55 @@ export default React.createClass({ | |||
} | |||
return false; | |||
}); | |||
}, | |||
} | |||
componentWillUnmount() { | |||
componentWillUnmount () { | |||
this.closeSearch(); | |||
key.unbind('s'); | |||
}, | |||
} | |||
openSearch() { | |||
openSearch = () => { | |||
document.addEventListener('click', this.onClickOutside); | |||
this.setState({ open: true }, this.renderSearchView); | |||
}, | |||
}; | |||
closeSearch() { | |||
closeSearch = () => { | |||
document.removeEventListener('click', this.onClickOutside); | |||
this.resetSearchView(); | |||
this.setState({ open: false }); | |||
}, | |||
}; | |||
renderSearchView() { | |||
renderSearchView = () => { | |||
const searchContainer = this.refs.container; | |||
this.searchView = new SearchView({ | |||
model: new Backbone.Model(this.props), | |||
hide: this.closeSearch | |||
}); | |||
this.searchView.render().$el.appendTo(searchContainer); | |||
}, | |||
}; | |||
resetSearchView() { | |||
resetSearchView = () => { | |||
if (this.searchView) { | |||
this.searchView.destroy(); | |||
} | |||
}, | |||
}; | |||
onClick(e) { | |||
onClick = e => { | |||
e.preventDefault(); | |||
if (this.state.open) { | |||
this.closeSearch(); | |||
} else { | |||
this.openSearch(); | |||
} | |||
}, | |||
}; | |||
onClickOutside(e) { | |||
onClickOutside = e => { | |||
if (!contains(this.refs.dropdown, e.target)) { | |||
this.closeSearch(); | |||
} | |||
}, | |||
}; | |||
render() { | |||
render () { | |||
const dropdownClassName = 'dropdown' + (this.state.open ? ' open' : ''); | |||
return ( | |||
<li ref="dropdown" className={dropdownClassName}> | |||
@@ -103,4 +103,10 @@ export default React.createClass({ | |||
</li> | |||
); | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
currentUser: getCurrentUser(state) | |||
}); | |||
export default connect(mapStateToProps)(GlobalNavSearch); |
@@ -22,13 +22,26 @@ import Avatar from '../../../../components/ui/Avatar'; | |||
import RecentHistory from '../component/RecentHistory'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
export default React.createClass({ | |||
renderAuthenticated() { | |||
export default class GlobalNavUser extends React.Component { | |||
handleLogin = e => { | |||
e.preventDefault(); | |||
const returnTo = window.location.pathname + window.location.search; | |||
window.location = `${window.baseUrl}/sessions/new?return_to=${encodeURIComponent(returnTo)}${window.location.hash}`; | |||
}; | |||
handleLogout = e => { | |||
e.preventDefault(); | |||
RecentHistory.clear(); | |||
window.location = `${window.baseUrl}/sessions/logout`; | |||
}; | |||
renderAuthenticated () { | |||
const { currentUser } = this.props; | |||
return ( | |||
<li className="dropdown js-user-authenticated"> | |||
<a className="dropdown-toggle" data-toggle="dropdown" href="#"> | |||
<Avatar email={window.SS.userEmail} size={20}/> | |||
{window.SS.userName} <i className="icon-dropdown"/> | |||
<Avatar email={currentUser.email} size={20}/> | |||
{currentUser.name} <i className="icon-dropdown"/> | |||
</a> | |||
<ul className="dropdown-menu dropdown-menu-right"> | |||
<li> | |||
@@ -40,30 +53,17 @@ export default React.createClass({ | |||
</ul> | |||
</li> | |||
); | |||
}, | |||
} | |||
renderAnonymous() { | |||
renderAnonymous () { | |||
return ( | |||
<li> | |||
<a onClick={this.handleLogin} href="#">{translate('layout.login')}</a> | |||
</li> | |||
); | |||
}, | |||
handleLogin(e) { | |||
e.preventDefault(); | |||
const returnTo = window.location.pathname + window.location.search; | |||
window.location = `${window.baseUrl}/sessions/new?return_to=${encodeURIComponent(returnTo)}${window.location.hash}`; | |||
}, | |||
handleLogout(e) { | |||
e.preventDefault(); | |||
RecentHistory.clear(); | |||
window.location = `${window.baseUrl}/sessions/logout`; | |||
}, | |||
} | |||
render() { | |||
const isUserAuthenticated = !!window.SS.user; | |||
return isUserAuthenticated ? this.renderAuthenticated() : this.renderAnonymous(); | |||
render () { | |||
return this.props.currentUser.isLoggedIn ? this.renderAuthenticated() : this.renderAnonymous(); | |||
} | |||
}); | |||
} |
@@ -28,6 +28,7 @@ import SearchTemplate from '../templates/nav-search.hbs'; | |||
import RecentHistory from '../component/RecentHistory'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { collapsedDirFromPath, fileFromPath } from '../../../../helpers/path'; | |||
import { isUserAdmin } from '../../../../helpers/users'; | |||
const SearchItemView = Marionette.ItemView.extend({ | |||
tagName: 'li', | |||
@@ -96,7 +97,7 @@ export default Marionette.LayoutView.extend({ | |||
const that = this; | |||
this.results = new Backbone.Collection(); | |||
this.favorite = []; | |||
if (window.SS.user) { | |||
if (this.model.get('currentUser').isLoggedIn) { | |||
this.fetchFavorite().always(function () { | |||
that.resetResultsToDefault(); | |||
}); | |||
@@ -226,7 +227,7 @@ export default Marionette.LayoutView.extend({ | |||
{ name: translate('quality_gates.page'), url: window.baseUrl + '/quality_gates' } | |||
]; | |||
const customItems = []; | |||
if (window.SS.isUserAdmin) { | |||
if (isUserAdmin(this.model.get('currentUser'))) { | |||
customItems.push({ name: translate('layout.settings'), url: window.baseUrl + '/settings' }); | |||
} | |||
const findings = [].concat(DEFAULT_ITEMS, customItems).filter(function (f) { |
@@ -0,0 +1,134 @@ | |||
/* | |||
* 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 classNames from 'classnames'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
export default class SettingsNav extends React.Component { | |||
static defaultProps = { | |||
extensions: [] | |||
}; | |||
isSomethingActive (urls) { | |||
const path = window.location.pathname; | |||
return urls.some(url => path.indexOf(window.baseUrl + url) === 0); | |||
} | |||
isSecurityActive () { | |||
const urls = ['/users', '/groups', '/roles/global', '/permission_templates']; | |||
return this.isSomethingActive(urls); | |||
} | |||
isProjectsActive () { | |||
const urls = ['/projects_admin', '/background_tasks']; | |||
return this.isSomethingActive(urls); | |||
} | |||
isSystemActive () { | |||
const urls = ['/updatecenter', '/system']; | |||
return this.isSomethingActive(urls); | |||
} | |||
renderLink(url, title, highlightUrl = url) { | |||
const fullUrl = window.baseUrl + url; | |||
const isActive = typeof highlightUrl === 'string' ? | |||
window.location.pathname.indexOf(window.baseUrl + highlightUrl) === 0 : | |||
highlightUrl(fullUrl); | |||
return ( | |||
<li key={url} className={classNames({ 'active': isActive })}> | |||
<a href={fullUrl}>{title}</a> | |||
</li> | |||
); | |||
} | |||
render () { | |||
const isSecurity = this.isSecurityActive(); | |||
const isProjects = this.isProjectsActive(); | |||
const isSystem = this.isSystemActive(); | |||
const securityClassName = classNames('dropdown', { active: isSecurity }); | |||
const projectsClassName = classNames('dropdown', { active: isProjects }); | |||
const systemClassName = classNames('dropdown', { active: isSystem }); | |||
const configurationClassNames = classNames('dropdown', { | |||
active: !isSecurity && !isProjects && !isSystem | |||
}); | |||
return ( | |||
<nav className="navbar navbar-context page-container" id="context-navigation"> | |||
<div className="navbar-context-inner"> | |||
<div className="container"> | |||
<ul className="nav navbar-nav nav-crumbs"> | |||
{this.renderLink('/settings', translate('layout.settings'))} | |||
</ul> | |||
<ul className="nav navbar-nav nav-tabs"> | |||
<li className={configurationClassNames}> | |||
<a className="dropdown-toggle" data-toggle="dropdown" href="#"> | |||
{translate('sidebar.project_settings')} <i className="icon-dropdown"/> | |||
</a> | |||
<ul className="dropdown-menu"> | |||
{this.renderLink('/settings', translate('settings.page'), url => window.location.pathname === url)} | |||
{this.renderLink('/settings/licenses', translate('property.category.licenses'))} | |||
{this.renderLink('/settings/encryption', translate('property.category.security.encryption'))} | |||
{this.renderLink('/settings/server_id', translate('property.category.server_id'))} | |||
{this.renderLink('/metrics', 'Custom Metrics')} | |||
{this.props.extensions.map(e => this.renderLink(e.url, e.name))} | |||
</ul> | |||
</li> | |||
<li className={securityClassName}> | |||
<a className="dropdown-toggle" data-toggle="dropdown" href="#"> | |||
{translate('sidebar.security')} <i className="icon-dropdown"/> | |||
</a> | |||
<ul className="dropdown-menu"> | |||
{this.renderLink('/users', translate('users.page'))} | |||
{this.renderLink('/groups', translate('user_groups.page'))} | |||
{this.renderLink('/roles/global', translate('global_permissions.page'))} | |||
{this.renderLink('/permission_templates', translate('permission_templates'))} | |||
</ul> | |||
</li> | |||
<li className={projectsClassName}> | |||
<a className="dropdown-toggle" data-toggle="dropdown" href="#"> | |||
{translate('sidebar.projects')} <i className="icon-dropdown"/> | |||
</a> | |||
<ul className="dropdown-menu"> | |||
{this.renderLink('/projects_admin', 'Management')} | |||
{this.renderLink('/background_tasks', translate('background_tasks.page'))} | |||
</ul> | |||
</li> | |||
<li className={systemClassName}> | |||
<a className="dropdown-toggle" data-toggle="dropdown" href="#"> | |||
{translate('sidebar.system')} <i className="icon-dropdown"/> | |||
</a> | |||
<ul className="dropdown-menu"> | |||
{this.renderLink('/updatecenter', translate('update_center.page'))} | |||
{this.renderLink('/system', translate('system_info.page'))} | |||
</ul> | |||
</li> | |||
</ul> | |||
</div> | |||
</div> | |||
</nav> | |||
); | |||
} | |||
} |
@@ -1,133 +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 classNames from 'classnames'; | |||
import some from 'lodash/some'; | |||
import LinksMixin from '../links-mixin'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
export default React.createClass({ | |||
mixins: [LinksMixin], | |||
getDefaultProps() { | |||
return { extensions: [] }; | |||
}, | |||
isSomethingActive(urls) { | |||
const path = window.location.pathname; | |||
return some(urls, url => path.indexOf(window.baseUrl + url) === 0); | |||
}, | |||
isSecurityActive() { | |||
const urls = ['/users', '/groups', '/roles/global', '/permission_templates']; | |||
return this.isSomethingActive(urls); | |||
}, | |||
isProjectsActive() { | |||
const urls = ['/projects_admin', '/background_tasks']; | |||
return this.isSomethingActive(urls); | |||
}, | |||
isSystemActive() { | |||
const urls = ['/updatecenter', '/system']; | |||
return this.isSomethingActive(urls); | |||
}, | |||
render() { | |||
const isSecurity = this.isSecurityActive(); | |||
const isProjects = this.isProjectsActive(); | |||
const isSystem = this.isSystemActive(); | |||
const securityClassName = classNames('dropdown', { active: isSecurity }); | |||
const projectsClassName = classNames('dropdown', { active: isProjects }); | |||
const systemClassName = classNames('dropdown', { active: isSystem }); | |||
const configurationClassNames = classNames('dropdown', { | |||
active: !isSecurity && !isProjects && !isSystem | |||
}); | |||
return ( | |||
<div className="container"> | |||
<ul className="nav navbar-nav nav-crumbs"> | |||
{this.renderLink('/settings', translate('layout.settings'))} | |||
</ul> | |||
<ul className="nav navbar-nav nav-tabs"> | |||
<li className={configurationClassNames}> | |||
<a className="dropdown-toggle" data-toggle="dropdown" href="#"> | |||
{translate('sidebar.project_settings')} | |||
{' '} | |||
<i className="icon-dropdown"></i> | |||
</a> | |||
<ul className="dropdown-menu"> | |||
{this.renderLink('/settings', translate('settings.page'), url => window.location.pathname === url)} | |||
{this.renderLink('/settings/licenses', translate('property.category.licenses'))} | |||
{this.renderLink('/settings/encryption', translate('property.category.security.encryption'))} | |||
{this.renderLink('/settings/server_id', translate('property.category.server_id'))} | |||
{this.renderLink('/metrics', 'Custom Metrics')} | |||
{this.props.extensions.map(e => this.renderLink(e.url, e.name))} | |||
</ul> | |||
</li> | |||
<li className={securityClassName}> | |||
<a className="dropdown-toggle" data-toggle="dropdown" href="#"> | |||
{translate('sidebar.security')} | |||
{' '} | |||
<i className="icon-dropdown"></i> | |||
</a> | |||
<ul className="dropdown-menu"> | |||
{this.renderLink('/users', translate('users.page'))} | |||
{this.renderLink('/groups', translate('user_groups.page'))} | |||
{this.renderLink('/roles/global', | |||
translate('global_permissions.page'))} | |||
{this.renderLink('/permission_templates', | |||
translate('permission_templates'))} | |||
</ul> | |||
</li> | |||
<li className={projectsClassName}> | |||
<a className="dropdown-toggle" data-toggle="dropdown" href="#"> | |||
{translate('sidebar.projects')} | |||
{' '} | |||
<i className="icon-dropdown"></i> | |||
</a> | |||
<ul className="dropdown-menu"> | |||
{this.renderLink('/projects_admin', 'Management')} | |||
{this.renderLink('/background_tasks', | |||
translate('background_tasks.page'))} | |||
</ul> | |||
</li> | |||
<li className={systemClassName}> | |||
<a className="dropdown-toggle" data-toggle="dropdown" href="#"> | |||
{translate('sidebar.system')} | |||
{' '} | |||
<i className="icon-dropdown"></i> | |||
</a> | |||
<ul className="dropdown-menu"> | |||
{this.renderLink('/updatecenter', translate('update_center.page'))} | |||
{this.renderLink('/system', translate('system_info.page'))} | |||
</ul> | |||
</li> | |||
</ul> | |||
</div> | |||
); | |||
} | |||
}); |
@@ -20,12 +20,22 @@ | |||
import configureLocale from './utils/configureLocale'; | |||
import exposeLibraries from './utils/exposeLibraries'; | |||
import startAjaxMonitoring from './utils/startAjaxMonitoring'; | |||
import startApp from './utils/startApp'; | |||
import startReactApp from './utils/startReactApp'; | |||
import { installGlobal } from '../helpers/l10n'; | |||
import './styles/index'; | |||
require('script!../libs/third-party/jquery-ui.js'); | |||
require('script!../libs/third-party/select2.js'); | |||
require('script!../libs/third-party/keymaster.js'); | |||
require('script!../libs/third-party/bootstrap/tooltip.js'); | |||
require('script!../libs/third-party/bootstrap/dropdown.js'); | |||
require('script!../libs/select2-jquery-ui-fix.js'); | |||
require('script!../libs/inputs.js'); | |||
require('script!../libs/jquery-isolated-scroll.js'); | |||
require('script!../libs/application.js'); | |||
configureLocale(); | |||
startAjaxMonitoring(); | |||
startApp(); | |||
installGlobal(); | |||
startReactApp(); | |||
exposeLibraries(); |
@@ -0,0 +1,75 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// @flow | |||
type AppState = { | |||
authenticationError: boolean, | |||
authorizationError: boolean, | |||
qualifiers: ?Array<string> | |||
}; | |||
export type Action = { | |||
type: string, | |||
appState: AppState | |||
} | |||
export const actions = { | |||
SET_APP_STATE: 'SET_APP_STATE', | |||
REQUIRE_AUTHENTICATION: 'REQUIRE_AUTHENTICATION', | |||
REQUIRE_AUTHORIZATION: 'REQUIRE_AUTHORIZATION' | |||
}; | |||
export const setAppState = (appState: AppState): Action => ({ | |||
type: actions.SET_APP_STATE, | |||
appState | |||
}); | |||
export const requireAuthentication = () => ({ | |||
type: actions.REQUIRE_AUTHENTICATION | |||
}); | |||
export const requireAuthorization = () => ({ | |||
type: actions.REQUIRE_AUTHORIZATION | |||
}); | |||
const defaultValue = { | |||
authenticationError: false, | |||
authorizationError: false, | |||
qualifiers: null | |||
}; | |||
export default (state: AppState = defaultValue, action: Action) => { | |||
if (action.type === actions.SET_APP_STATE) { | |||
return { ...state, ...action.appState }; | |||
} | |||
if (action.type === actions.REQUIRE_AUTHENTICATION) { | |||
return { ...state, authenticationError: true }; | |||
} | |||
if (action.type === actions.REQUIRE_AUTHORIZATION) { | |||
return { ...state, authorizationError: true }; | |||
} | |||
return state; | |||
}; | |||
export const getRootQualifiers = (state: AppState) => ( | |||
state.qualifiers | |||
); |
@@ -18,13 +18,24 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { getLanguages } from '../../api/languages'; | |||
import { getGlobalNavigation, getComponentNavigation } from '../../api/nav'; | |||
import * as auth from '../../api/auth'; | |||
import { receiveLanguages } from './languages/actions'; | |||
import { receiveComponents } from './components/actions'; | |||
import { addGlobalErrorMessage } from '../../components/store/globalMessages'; | |||
import { parseError } from '../../apps/code/utils'; | |||
import { setAppState } from './appState/duck'; | |||
const onFail = dispatch => error => { | |||
parseError(error).then(message => dispatch(addGlobalErrorMessage(message))); | |||
}; | |||
const onFail = dispatch => error => ( | |||
parseError(error).then(message => dispatch(addGlobalErrorMessage(message))) | |||
); | |||
export const fetchAppState = () => dispatch => ( | |||
getGlobalNavigation().then( | |||
appState => dispatch(setAppState(appState)), | |||
onFail(dispatch) | |||
) | |||
); | |||
export const fetchLanguages = () => dispatch => { | |||
return getLanguages().then( | |||
@@ -32,3 +43,37 @@ export const fetchLanguages = () => dispatch => { | |||
onFail(dispatch) | |||
); | |||
}; | |||
const mapUuidToId = project => ({ ...project, id: project.uuid }); | |||
const addQualifier = project => ({ | |||
...project, | |||
qualifier: project.breadcrumbs[project.breadcrumbs.length - 1].qualifier | |||
}); | |||
export const fetchProject = key => dispatch => ( | |||
getComponentNavigation(key).then( | |||
component => dispatch(receiveComponents([mapUuidToId(addQualifier(component))])), | |||
onFail(dispatch) | |||
) | |||
); | |||
export const doLogin = (login, password) => dispatch => ( | |||
auth.login(login, password).then( | |||
() => { /* everything is fine */ }, | |||
() => { | |||
dispatch(addGlobalErrorMessage('Authentication failed')); | |||
return Promise.reject(); | |||
} | |||
) | |||
); | |||
export const doLogout = () => dispatch => ( | |||
auth.logout().then( | |||
() => { /* everything is fine */ }, | |||
() => { | |||
dispatch(addGlobalErrorMessage('Logout failed')); | |||
return Promise.reject(); | |||
} | |||
) | |||
); |
@@ -18,6 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { combineReducers } from 'redux'; | |||
import appState from './appState/duck'; | |||
import components, * as fromComponents from './components/reducer'; | |||
import users, * as fromUsers from './users/reducer'; | |||
import favorites, * as fromFavorites from './favorites/duck'; | |||
@@ -33,6 +34,7 @@ import qualityGatesApp from '../../apps/quality-gates/store/rootReducer'; | |||
import settingsApp, * as fromSettingsApp from '../../apps/settings/store/rootReducer'; | |||
export default combineReducers({ | |||
appState, | |||
components, | |||
globalMessages, | |||
favorites, | |||
@@ -49,6 +51,10 @@ export default combineReducers({ | |||
settingsApp | |||
}); | |||
export const getAppState = state => ( | |||
state.appState | |||
); | |||
export const getComponent = (state, key) => ( | |||
fromComponents.getComponent(state.components, key) | |||
); | |||
@@ -125,6 +131,10 @@ export const getPermissionsAppError = state => ( | |||
fromPermissionsApp.getError(state.permissionsApp) | |||
); | |||
export const getSettingValue = (state, key) => ( | |||
fromSettingsApp.getValue(state.settingsApp, key) | |||
); | |||
export const getSettingsAppDefinition = (state, key) => ( | |||
fromSettingsApp.getDefinition(state.settingsApp, key) | |||
); |
@@ -26,6 +26,6 @@ export const receiveCurrentUser = user => ({ | |||
user | |||
}); | |||
export const fetchCurrentUser = () => dispatch => { | |||
getCurrentUser().then(user => dispatch(receiveCurrentUser(user))); | |||
}; | |||
export const fetchCurrentUser = () => dispatch => ( | |||
getCurrentUser().then(user => dispatch(receiveCurrentUser(user))) | |||
); |
@@ -39,7 +39,7 @@ const userLogins = (state = [], action = {}) => { | |||
const currentUser = (state = null, action = {}) => { | |||
if (action.type === RECEIVE_CURRENT_USER) { | |||
return action.user.isLoggedIn ? action.user.login : false; | |||
return action.user; | |||
} | |||
return state; | |||
@@ -48,5 +48,5 @@ const currentUser = (state = null, action = {}) => { | |||
export default combineReducers({ usersByLogin, userLogins, currentUser }); | |||
export const getCurrentUser = state => ( | |||
state.currentUser ? state.usersByLogin[state.currentUser] : state.currentUser | |||
state.currentUser | |||
); |
@@ -17,16 +17,14 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { translate } from '../../../helpers/l10n'; | |||
// @flow | |||
import { getCurrentUser } from '../store/rootReducer'; | |||
export default { | |||
getLocalizedDashboardName(baseName) { | |||
const l10nKey = `dashboard.${baseName}.name`; | |||
const l10nLabel = translate(l10nKey); | |||
if (l10nLabel !== l10nKey) { | |||
return l10nLabel; | |||
} else { | |||
return baseName; | |||
} | |||
} | |||
// TODO remove my usages | |||
const getCurrentUserFromStore = () => { | |||
const getStore = require('./getStore').default; | |||
const store = getStore(); | |||
return getCurrentUser(store.getState()); | |||
}; | |||
export default getCurrentUserFromStore; |
@@ -0,0 +1,35 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// @flow | |||
import { useRouterHistory } from 'react-router'; | |||
import { createHistory } from 'history'; | |||
let history; | |||
const ensureHistory = () => { | |||
history = useRouterHistory(createHistory)({ | |||
basename: window.baseUrl + '/' | |||
}); | |||
return history; | |||
}; | |||
export default () => ( | |||
history ? history : ensureHistory() | |||
); |
@@ -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. | |||
*/ | |||
// @flow | |||
import configureStore from '../../components/store/configureStore'; | |||
import rootReducer from '../store/rootReducer'; | |||
let store; | |||
const createStore = () => { | |||
store = configureStore(rootReducer); | |||
return store; | |||
}; | |||
export default () => ( | |||
store ? store : createStore() | |||
); |
@@ -0,0 +1,36 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// @flow | |||
import getStore from './getStore'; | |||
import getHistory from './getHistory'; | |||
import { requireAuthentication } from '../store/appState/duck'; | |||
export default () => { | |||
const store = getStore(); | |||
const history = getHistory(); | |||
const returnTo = window.location.pathname + window.location.search + window.location.hash; | |||
store.dispatch(requireAuthentication()); | |||
history.replace({ | |||
pathname: '/sessions/new', | |||
query: { 'return_to': returnTo } | |||
}); | |||
}; |
@@ -0,0 +1,36 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// @flow | |||
import getStore from './getStore'; | |||
import getHistory from './getHistory'; | |||
import { requireAuthorization } from '../store/appState/duck'; | |||
export default () => { | |||
const store = getStore(); | |||
const history = getHistory(); | |||
const returnTo = window.location.pathname + window.location.search + window.location.hash; | |||
store.dispatch(requireAuthorization()); | |||
history.replace({ | |||
pathname: '/sessions/new', | |||
query: { 'return_to': returnTo } | |||
}); | |||
}; |
@@ -17,6 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
export default function () { | |||
return null; | |||
} | |||
// @flow | |||
export default () => { | |||
window.location = window.baseUrl + '/maintenance'; | |||
}; |
@@ -1,69 +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. | |||
*/ | |||
const knownPaths = [ | |||
'about', | |||
'account', | |||
'background_tasks', | |||
'coding_rules', | |||
'dashboard', | |||
'groups', | |||
'issues', | |||
'maintenance', | |||
'metrics', | |||
'permission_templates', | |||
'projects', | |||
'projects_admin', | |||
'roles/global', | |||
'settings', | |||
'setup', | |||
'system', | |||
'quality_gates', | |||
'profiles', | |||
'updatecenter', | |||
'users', | |||
'web_api', | |||
'code', | |||
'component_issues', | |||
'component_measures', | |||
'custom_measures', | |||
'project/background_tasks', | |||
'project/settings', | |||
'project/deletion', | |||
'project/quality_profiles', | |||
'project/quality_gate', | |||
'project/links', | |||
'project/key', | |||
'project_roles' | |||
]; | |||
const ignoredPaths = [ | |||
'users/new' | |||
]; | |||
export default function () { | |||
const currentPath = window.location.pathname; | |||
const isIgnored = ignoredPaths.some(path => currentPath.indexOf(`${window.baseUrl}/${path}`) === 0); | |||
if (isIgnored) { | |||
return false; | |||
} | |||
return knownPaths.some(path => currentPath.indexOf(`${window.baseUrl}/${path}`) === 0); | |||
} |
@@ -165,6 +165,12 @@ function handleAjaxError (jqXHR) { | |||
} | |||
} | |||
function handleNotAuthenticatedError () { | |||
// workaround cyclic dependencies | |||
const handleRequiredAuthentication = require('./handleRequiredAuthentication').default; | |||
handleRequiredAuthentication(); | |||
} | |||
$.ajaxSetup({ | |||
beforeSend (jqXHR) { | |||
jqXHR.setRequestHeader(getCSRFTokenName(), getCSRFTokenValue()); | |||
@@ -177,6 +183,7 @@ $.ajaxSetup({ | |||
}, | |||
statusCode: { | |||
400: handleAjaxError, | |||
401: handleNotAuthenticatedError, | |||
403: handleAjaxError, | |||
500: handleAjaxError, | |||
502: handleAjaxError, |
@@ -1,69 +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 Navigation from '../components/nav/app'; | |||
import { installGlobal, requestMessages } from '../../helpers/l10n'; | |||
function requestLocalizationBundle () { | |||
if (!window.sonarqube.bannedNavigation) { | |||
installGlobal(); | |||
return requestMessages(); | |||
} else { | |||
return Promise.resolve(); | |||
} | |||
} | |||
function startNavigation () { | |||
if (!window.sonarqube.bannedNavigation) { | |||
return new Navigation().start(); | |||
} else { | |||
return Promise.resolve(); | |||
} | |||
} | |||
function prepareAppOptions (navResponse) { | |||
const appOptions = { el: '#content' }; | |||
if (navResponse) { | |||
appOptions.rootQualifiers = navResponse.global.qualifiers; | |||
appOptions.logoUrl = navResponse.global.logoUrl; | |||
appOptions.logoWidth = navResponse.global.logoWidth; | |||
if (navResponse.component) { | |||
appOptions.component = { | |||
id: navResponse.component.uuid, | |||
key: navResponse.component.key, | |||
name: navResponse.component.name, | |||
qualifier: _.last(navResponse.component.breadcrumbs).qualifier, | |||
breadcrumbs: navResponse.component.breadcrumbs, | |||
snapshotDate: navResponse.component.snapshotDate | |||
}; | |||
} | |||
} | |||
return appOptions; | |||
} | |||
const startApp = () => { | |||
window.sonarqube.appStarted = Promise.resolve() | |||
.then(requestLocalizationBundle) | |||
.then(startNavigation) | |||
.then(prepareAppOptions); | |||
}; | |||
export default startApp; | |||
@@ -19,17 +19,23 @@ | |||
*/ | |||
import React from 'react'; | |||
import { render } from 'react-dom'; | |||
import { Router, Route, useRouterHistory } from 'react-router'; | |||
import { createHistory } from 'history'; | |||
import { Router, Route, IndexRoute } from 'react-router'; | |||
import { Provider } from 'react-redux'; | |||
import LocalizationContainer from '../components/LocalizationContainer'; | |||
import MigrationContainer from '../components/MigrationContainer'; | |||
import App from '../components/App'; | |||
import ComponentContainer from '../components/ComponentContainer'; | |||
import NullComponent from '../components/NullComponent'; | |||
import GlobalContainer from '../components/GlobalContainer'; | |||
import SimpleContainer from '../components/SimpleContainer'; | |||
import Landing from '../components/Landing'; | |||
import ProjectContainer from '../components/ProjectContainer'; | |||
import AdminContainer from '../components/AdminContainer'; | |||
import NotFound from '../components/NotFound'; | |||
import aboutRoutes from '../../apps/about/routes'; | |||
import accountRoutes from '../../apps/account/routes'; | |||
import backgroundTasksRoutes from '../../apps/background-tasks/routes'; | |||
import codeRoutes from '../../apps/code/routes'; | |||
import codingRulesRoutes from '../../apps/coding-rules/routes'; | |||
import componentRoutes from '../../apps/component/routes'; | |||
import componentIssuesRoutes from '../../apps/component-issues/routes'; | |||
import componentMeasuresRoutes from '../../apps/component-measures/routes'; | |||
import customMeasuresRoutes from '../../apps/custom-measures/routes'; | |||
@@ -43,6 +49,7 @@ import projectsRoutes from '../../apps/projects/routes'; | |||
import projectsAdminRoutes from '../../apps/projects-admin/routes'; | |||
import qualityGatesRoutes from '../../apps/quality-gates/routes'; | |||
import qualityProfilesRoutes from '../../apps/quality-profiles/routes'; | |||
import sessionsRoutes from '../../apps/sessions/routes'; | |||
import settingsRoutes from '../../apps/settings/routes'; | |||
import systemRoutes from '../../apps/system/routes'; | |||
import updateCenterRoutes from '../../apps/update-center/routes'; | |||
@@ -50,67 +57,79 @@ import usersRoutes from '../../apps/users/routes'; | |||
import webAPIRoutes from '../../apps/web-api/routes'; | |||
import { maintenanceRoutes, setupRoutes } from '../../apps/maintenance/routes'; | |||
import { globalPermissionsRoutes, projectPermissionsRoutes } from '../../apps/permissions/routes'; | |||
import configureStore from '../../components/store/configureStore'; | |||
import rootReducer from '../store/rootReducer'; | |||
import isCurrentPathKnown from './isCurrentPathKnown'; | |||
import getStore from './getStore'; | |||
import getHistory from './getHistory'; | |||
const startReactApp = () => { | |||
if (isCurrentPathKnown()) { | |||
window.sonarqube.appStarted.then(options => { | |||
const el = document.querySelector(options.el); | |||
const el = document.getElementById('content'); | |||
const history = useRouterHistory(createHistory)({ | |||
basename: window.baseUrl + '/' | |||
}); | |||
const history = getHistory(); | |||
const store = getStore(); | |||
const store = configureStore(rootReducer); | |||
render(( | |||
<Provider store={store}> | |||
<Router history={history}> | |||
<Route component={LocalizationContainer}> | |||
<Route component={SimpleContainer}> | |||
<Route path="maintenance">{maintenanceRoutes}</Route> | |||
<Route path="setup">{setupRoutes}</Route> | |||
</Route> | |||
<Route component={MigrationContainer}> | |||
<Route component={SimpleContainer}> | |||
<Route path="/sessions">{sessionsRoutes}</Route> | |||
</Route> | |||
render(( | |||
<Provider store={store}> | |||
<Router history={history}> | |||
<Route path="/" component={App}> | |||
<Route path="about">{aboutRoutes}</Route> | |||
<Route path="account">{accountRoutes}</Route> | |||
<Route path="background_tasks">{backgroundTasksRoutes}</Route> | |||
<Route path="coding_rules">{codingRulesRoutes}</Route> | |||
<Route path="dashboard">{overviewRoutes}</Route> | |||
<Route path="groups">{groupsRoutes}</Route> | |||
<Route path="issues">{issuesRoutes}</Route> | |||
<Route path="maintenance">{maintenanceRoutes}</Route> | |||
<Route path="metrics">{metricsRoutes}</Route> | |||
<Route path="permission_templates">{permissionTemplatesRoutes}</Route> | |||
<Route path="projects">{projectsRoutes}</Route> | |||
<Route path="projects_admin">{projectsAdminRoutes}</Route> | |||
<Route path="roles/global">{globalPermissionsRoutes}</Route> | |||
<Route path="settings">{settingsRoutes}</Route> | |||
<Route path="setup">{setupRoutes}</Route> | |||
<Route path="system">{systemRoutes}</Route> | |||
<Route path="quality_gates">{qualityGatesRoutes}</Route> | |||
<Route path="profiles">{qualityProfilesRoutes}</Route> | |||
<Route path="updatecenter">{updateCenterRoutes}</Route> | |||
<Route path="users">{usersRoutes}</Route> | |||
<Route path="web_api">{webAPIRoutes}</Route> | |||
<Route component={ComponentContainer}> | |||
<Route path="code">{codeRoutes}</Route> | |||
<Route path="component_issues">{componentIssuesRoutes}</Route> | |||
<Route path="component_measures">{componentMeasuresRoutes}</Route> | |||
<Route path="custom_measures">{customMeasuresRoutes}</Route> | |||
<Route path="project"> | |||
<IndexRoute component={Landing}/> | |||
<Route component={GlobalContainer}> | |||
<Route path="about">{aboutRoutes}</Route> | |||
<Route path="account">{accountRoutes}</Route> | |||
<Route path="coding_rules">{codingRulesRoutes}</Route> | |||
<Route path="component">{componentRoutes}</Route> | |||
<Route path="issues">{issuesRoutes}</Route> | |||
<Route path="projects">{projectsRoutes}</Route> | |||
<Route path="quality_gates">{qualityGatesRoutes}</Route> | |||
<Route path="profiles">{qualityProfilesRoutes}</Route> | |||
<Route path="web_api">{webAPIRoutes}</Route> | |||
<Route component={ProjectContainer}> | |||
<Route path="code">{codeRoutes}</Route> | |||
<Route path="component_issues">{componentIssuesRoutes}</Route> | |||
<Route path="component_measures">{componentMeasuresRoutes}</Route> | |||
<Route path="custom_measures">{customMeasuresRoutes}</Route> | |||
<Route path="dashboard">{overviewRoutes}</Route> | |||
<Route path="project"> | |||
<Route path="background_tasks">{backgroundTasksRoutes}</Route> | |||
<Route path="settings">{settingsRoutes}</Route> | |||
{projectAdminRoutes} | |||
</Route> | |||
<Route path="project_roles">{projectPermissionsRoutes}</Route> | |||
</Route> | |||
<Route component={AdminContainer}> | |||
<Route path="background_tasks">{backgroundTasksRoutes}</Route> | |||
<Route path="groups">{groupsRoutes}</Route> | |||
<Route path="metrics">{metricsRoutes}</Route> | |||
<Route path="permission_templates">{permissionTemplatesRoutes}</Route> | |||
<Route path="projects_admin">{projectsAdminRoutes}</Route> | |||
<Route path="roles/global">{globalPermissionsRoutes}</Route> | |||
<Route path="settings">{settingsRoutes}</Route> | |||
{projectAdminRoutes} | |||
<Route path="system">{systemRoutes}</Route> | |||
<Route path="updatecenter">{updateCenterRoutes}</Route> | |||
<Route path="users">{usersRoutes}</Route> | |||
</Route> | |||
<Route path="project_roles">{projectPermissionsRoutes}</Route> | |||
</Route> | |||
</Route> | |||
<Route path="*" component={NullComponent}/> | |||
</Router> | |||
</Provider> | |||
), el); | |||
}); | |||
} | |||
<Route path="*" component={NotFound}/> | |||
</Route> | |||
</Route> | |||
</Route> | |||
</Router> | |||
</Provider> | |||
), el); | |||
}; | |||
export default startReactApp; |
@@ -18,6 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import keyBy from 'lodash/keyBy'; | |||
import AboutProjects from './AboutProjects'; | |||
import EntryIssueTypes from './EntryIssueTypes'; | |||
@@ -28,11 +29,18 @@ import AboutLeakPeriod from './AboutLeakPeriod'; | |||
import AboutStandards from './AboutStandards'; | |||
import AboutScanners from './AboutScanners'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import '../styles.css'; | |||
import { searchProjects } from '../../../api/components'; | |||
import { getFacet } from '../../../api/issues'; | |||
import { getSettingValue } from '../../../app/store/rootReducer'; | |||
import * as settingsAPI from '../../../api/settings'; | |||
import '../styles.css'; | |||
class AboutApp extends React.Component { | |||
static propTypes = { | |||
customLogoUrl: React.PropTypes.string, | |||
customLogoWidth: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]) | |||
}; | |||
export default class AboutApp extends React.Component { | |||
state = { | |||
loading: true | |||
}; | |||
@@ -56,20 +64,23 @@ export default class AboutApp extends React.Component { | |||
return getFacet({ resolved: false }, 'types'); | |||
} | |||
loadCustomText () { | |||
return settingsAPI.getSettingValue('sonar.lf.aboutText'); | |||
} | |||
loadData () { | |||
Promise.all([ | |||
window.sonarqube.appStarted, | |||
this.loadProjects(), | |||
this.loadIssues() | |||
this.loadIssues(), | |||
this.loadCustomText() | |||
]).then(responses => { | |||
if (this.mounted) { | |||
const [options, projectsCount, issues] = responses; | |||
const [projectsCount, issues, customText] = responses; | |||
const issueTypes = keyBy(issues.facet, 'val'); | |||
this.setState({ | |||
projectsCount, | |||
issueTypes, | |||
logoUrl: options.logoUrl, | |||
logoWidth: options.logoWidth, | |||
customText, | |||
loading: false | |||
}); | |||
} | |||
@@ -81,12 +92,12 @@ export default class AboutApp extends React.Component { | |||
return null; | |||
} | |||
const { landingText } = window.sonarqube; | |||
const { customText } = this.state; | |||
const logoUrl = this.state.logoUrl || `${window.baseUrl}/images/logo.svg`; | |||
const logoWidth = this.state.logoWidth || 100; | |||
const logoUrl = this.props.customLogoUrl || `${window.baseUrl}/images/logo.svg`; | |||
const logoWidth = Number(this.props.customLogoWidth || 100); | |||
const logoHeight = 30; | |||
const logoTitle = this.state.logoUrl ? '' : translate('layout.sonar.slogan'); | |||
const logoTitle = this.props.customLogoUrl ? '' : translate('layout.sonar.slogan'); | |||
return ( | |||
<div id="about-page" className="about-page"> | |||
@@ -113,8 +124,8 @@ export default class AboutApp extends React.Component { | |||
<div className="about-page-container"> | |||
{landingText.length > 0 && ( | |||
<div className="about-page-section" dangerouslySetInnerHTML={{ __html: landingText }}/> | |||
{customText != null && customText.length > 0 && ( | |||
<div className="about-page-section" dangerouslySetInnerHTML={{ __html: customText }}/> | |||
)} | |||
<div className="columns"> | |||
@@ -142,3 +153,10 @@ export default class AboutApp extends React.Component { | |||
); | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
customLogoUrl: (getSettingValue(state, 'sonar.lf.logoUrl') || {}).value, | |||
customLogoWidth: (getSettingValue(state, 'sonar.lf.logoWidthPx') || {}).value | |||
}); | |||
export default connect(mapStateToProps)(AboutApp); |
@@ -28,16 +28,6 @@ class Account extends React.Component { | |||
render () { | |||
const { currentUser, children } = this.props; | |||
if (currentUser == null) { | |||
return ( | |||
<div id="account-page"> | |||
<div className="text-center"> | |||
<i className="spinner"/> | |||
</div> | |||
</div> | |||
); | |||
} | |||
return ( | |||
<div id="account-page"> | |||
<header className="account-header"> |
@@ -17,11 +17,11 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
/* @flow */ | |||
// @flow | |||
import React from 'react'; | |||
import shallowCompare from 'react-addons-shallow-compare'; | |||
import debounce from 'lodash/debounce'; | |||
import { connect } from 'react-redux'; | |||
import { DEFAULT_FILTERS, DEBOUNCE_DELAY, STATUSES, CURRENTS } from './../constants'; | |||
import Header from './Header'; | |||
import Footer from './Footer'; | |||
@@ -31,9 +31,10 @@ import Tasks from '../components/Tasks'; | |||
import { getTypes, getActivity, getStatus, cancelAllTasks, cancelTask as cancelTaskAPI } from '../../../api/ce'; | |||
import { updateTask, mapFiltersToParameters } from '../utils'; | |||
import { Task } from '../types'; | |||
import { getComponent } from '../../../app/store/rootReducer'; | |||
import '../background-tasks.css'; | |||
export default class BackgroundTasksApp extends React.Component { | |||
class BackgroundTasksApp extends React.Component { | |||
static contextTypes = { | |||
router: React.PropTypes.object.isRequired | |||
}; | |||
@@ -229,3 +230,9 @@ export default class BackgroundTasksApp extends React.Component { | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state, ownProps) => ({ | |||
component: ownProps.location.query.id ? getComponent(state, ownProps.location.query.id) : undefined | |||
}); | |||
export default connect(mapStateToProps)(BackgroundTasksApp); |
@@ -19,7 +19,7 @@ | |||
*/ | |||
import classNames from 'classnames'; | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import Components from './Components'; | |||
import Breadcrumbs from './Breadcrumbs'; | |||
import SourceViewer from './../../../components/source-viewer/SourceViewer'; | |||
@@ -27,10 +27,10 @@ import Search from './Search'; | |||
import ListFooter from '../../../components/controls/ListFooter'; | |||
import { retrieveComponentChildren, retrieveComponent, loadMoreChildren, parseError } from '../utils'; | |||
import { addComponent, addComponentBreadcrumbs } from '../bucket'; | |||
import { getComponent } from '../../../app/store/rootReducer'; | |||
import '../code.css'; | |||
export default class App extends React.Component { | |||
class App extends React.Component { | |||
state = { | |||
loading: true, | |||
baseComponent: null, | |||
@@ -210,3 +210,9 @@ export default class App extends React.Component { | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state, ownProps) => ({ | |||
component: getComponent(state, ownProps.location.query.id) | |||
}); | |||
export default connect(mapStateToProps)(App); |
@@ -19,13 +19,22 @@ | |||
*/ | |||
import React from 'react'; | |||
import init from '../init'; | |||
import { connect } from 'react-redux'; | |||
import { getComponent, getCurrentUser } from '../../../app/store/rootReducer'; | |||
export default class ComponentIssuesAppContainer extends React.Component { | |||
class ComponentIssuesAppContainer extends React.Component { | |||
componentDidMount () { | |||
init(this.refs.container); | |||
init(this.refs.container, this.props.component, this.props.currentUser); | |||
} | |||
render () { | |||
return <div ref="container"/>; | |||
} | |||
} | |||
const mapStateToProps = (state, ownProps) => ({ | |||
component: getComponent(state, ownProps.location.query.id), | |||
currentUser: getCurrentUser(state) | |||
}); | |||
export default connect(mapStateToProps)(ComponentIssuesAppContainer); |
@@ -33,17 +33,19 @@ import FacetsView from './../issues/facets-view'; | |||
import HeaderView from './../issues/HeaderView'; | |||
const App = new Marionette.Application(); | |||
const init = function (el) { | |||
const options = window.sonarqube; | |||
this.config = options.config; | |||
const init = function ({ el, component, currentUser }) { | |||
this.config = { | |||
resource: component.id, | |||
resourceName: component.name, | |||
resourceQualifier: component.qualifier | |||
}; | |||
this.state = new State({ | |||
canBulkChange: !!window.SS.user, | |||
canBulkChange: currentUser.isLoggedIn, | |||
isContext: true, | |||
contextQuery: { componentUuids: options.config.resource }, | |||
contextComponentUuid: options.config.resource, | |||
contextComponentName: options.config.resourceName, | |||
contextComponentQualifier: options.config.resourceQualifier | |||
contextQuery: { componentUuids: this.config.resource }, | |||
contextComponentUuid: this.config.resource, | |||
contextComponentName: this.config.resourceName, | |||
contextComponentQualifier: this.config.resourceQualifier | |||
}); | |||
this.updateContextFacets(); | |||
this.list = new Issues(); | |||
@@ -109,10 +111,10 @@ App.updateContextFacets = function () { | |||
}); | |||
}; | |||
App.on('start', function (el) { | |||
init.call(App, el); | |||
App.on('start', function (options) { | |||
init.call(App, options); | |||
}); | |||
export default function (el) { | |||
App.start(el); | |||
export default function (el, component, currentUser) { | |||
App.start({ el, component, currentUser }); | |||
} |
@@ -20,13 +20,12 @@ | |||
import { connect } from 'react-redux'; | |||
import App from './App'; | |||
import { fetchMetrics, setComponent } from './actions'; | |||
import { getMeasuresAppAllMetrics } from '../../../app/store/rootReducer'; | |||
import { getComponent, getMeasuresAppAllMetrics } from '../../../app/store/rootReducer'; | |||
const mapStateToProps = state => { | |||
return { | |||
metrics: getMeasuresAppAllMetrics(state) | |||
}; | |||
}; | |||
const mapStateToProps = (state, ownProps) => ({ | |||
component: getComponent(state, ownProps.location.query.id), | |||
metrics: getMeasuresAppAllMetrics(state) | |||
}); | |||
const mapDispatchToProps = dispatch => { | |||
return { |
@@ -17,15 +17,18 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import SourceViewer from '../../../components/source-viewer/SourceViewer'; | |||
import { getComponentNavigation } from '../../../api/nav'; | |||
export default class ComponentContainer extends React.Component { | |||
export default class App extends React.Component { | |||
state = {}; | |||
componentDidMount () { | |||
window.sonarqube.appStarted.then(options => { | |||
this.setState({ component: options.component }); | |||
}); | |||
getComponentNavigation(this.props.location.query.id).then(component => ( | |||
this.setState({ component }) | |||
)); | |||
} | |||
render () { | |||
@@ -33,8 +36,10 @@ export default class ComponentContainer extends React.Component { | |||
return null; | |||
} | |||
return React.cloneElement(this.props.children, { | |||
component: this.state.component | |||
}); | |||
return ( | |||
<div className="page"> | |||
<SourceViewer component={{ id: this.state.component.uuid }}/> | |||
</div> | |||
); | |||
} | |||
} |
@@ -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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { IndexRoute, Redirect } from 'react-router'; | |||
import App from './components/App'; | |||
export default [ | |||
<Redirect key="1" from="/component/index" to="/component"/>, | |||
<IndexRoute key="2" component={App}/> | |||
]; |
@@ -18,22 +18,22 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import init from '../init'; | |||
import { getComponent } from '../../../app/store/rootReducer'; | |||
export default class CustomMeasuresAppContainer extends React.Component { | |||
class CustomMeasuresAppContainer extends React.Component { | |||
componentDidMount () { | |||
if (this.props.component) { | |||
init(this.refs.container, this.props.component); | |||
} | |||
} | |||
componentDidUpdate () { | |||
if (this.props.component) { | |||
init(this.refs.container, this.props.component); | |||
} | |||
init(this.refs.container, this.props.component); | |||
} | |||
render () { | |||
return <div ref="container"/>; | |||
} | |||
} | |||
const mapStateToProps = (state, ownProps) => ({ | |||
component: getComponent(state, ownProps.location.query.id) | |||
}); | |||
export default connect(mapStateToProps)(CustomMeasuresAppContainer); |
@@ -54,7 +54,7 @@ export default Marionette.ItemView.extend({ | |||
...Marionette.ItemView.prototype.serializeData.apply(this, arguments), | |||
me, | |||
isContext: this.options.app.state.get('isContext'), | |||
user: window.SS.user | |||
user: this.options.app.state.get('user') | |||
}; | |||
} | |||
}); |
@@ -18,14 +18,26 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import init from '../init'; | |||
import { getCurrentUser } from '../../../app/store/rootReducer'; | |||
class IssuesAppContainer extends React.Component { | |||
static propTypes = { | |||
currentUser: React.PropTypes.any.isRequired | |||
}; | |||
export default class IssuesAppContainer extends React.Component { | |||
componentDidMount () { | |||
init(this.refs.container); | |||
init(this.refs.container, this.props.currentUser); | |||
} | |||
render () { | |||
return <div ref="container"/>; | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
currentUser: getCurrentUser(state) | |||
}); | |||
export default connect(mapStateToProps)(IssuesAppContainer); |
@@ -32,8 +32,8 @@ import FacetsView from './facets-view'; | |||
import HeaderView from './HeaderView'; | |||
const App = new Marionette.Application(); | |||
const init = function (el) { | |||
this.state = new State({ canBulkChange: !!window.SS.user }); | |||
const init = function ({ el, user }) { | |||
this.state = new State({ user, canBulkChange: user.isLoggedIn }); | |||
this.list = new Issues(); | |||
this.facets = new Facets(); | |||
@@ -76,7 +76,7 @@ App.on('start', function (el) { | |||
init.call(App, el); | |||
}); | |||
export default function (el) { | |||
App.start(el); | |||
export default function (el, user) { | |||
App.start({ el, user }); | |||
} | |||
@@ -19,7 +19,6 @@ | |||
*/ | |||
import React from 'react'; | |||
import shallowCompare from 'react-addons-shallow-compare'; | |||
import OverviewApp from './OverviewApp'; | |||
import EmptyOverview from './EmptyOverview'; | |||
import { ComponentType } from '../propTypes'; | |||
@@ -36,6 +35,15 @@ export default class App extends React.Component { | |||
render () { | |||
const { component } = this.props; | |||
if (['FIL', 'UTS'].includes(component.qualifier)) { | |||
const SourceViewer = require('../../../components/source-viewer/SourceViewer').default; | |||
return ( | |||
<div className="page"> | |||
<SourceViewer component={component}/> | |||
</div> | |||
); | |||
} | |||
if (!component.snapshotDate) { | |||
return <EmptyOverview {...this.props}/>; | |||
} |
@@ -17,32 +17,12 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import App from './App'; | |||
import { getComponent } from '../../../app/store/rootReducer'; | |||
export default class AppContainer extends React.Component { | |||
state = {}; | |||
const mapStateToProps = (state, ownProps) => ({ | |||
component: getComponent(state, ownProps.location.query.id) | |||
}); | |||
componentDidMount () { | |||
window.sonarqube.appStarted.then(options => { | |||
this.setState({ component: options.component }); | |||
}); | |||
} | |||
render () { | |||
// workaround for the case when a file is displayed | |||
if (window.sonarqube.file) { | |||
return null; | |||
} | |||
if (!this.state.component) { | |||
return null; | |||
} | |||
const component = { ...this.state.component, ...window.sonarqube.overview.component }; | |||
return ( | |||
<App component={component}/> | |||
); | |||
} | |||
} | |||
export default connect(mapStateToProps)(App); |
@@ -27,15 +27,15 @@ import EventsList from './../events/EventsList'; | |||
import MetaSize from './MetaSize'; | |||
const Meta = ({ component, measures }) => { | |||
const { qualifier, description, profiles, gate } = component; | |||
const { qualifier, description, qualityProfiles, qualityGate } = component; | |||
const isProject = qualifier === 'TRK'; | |||
const isView = qualifier === 'VW' || qualifier === 'SVW'; | |||
const isDeveloper = qualifier === 'DEV'; | |||
const hasDescription = !!description; | |||
const hasQualityProfiles = Array.isArray(profiles) && profiles.length > 0; | |||
const hasQualityGate = !!gate; | |||
const hasQualityProfiles = Array.isArray(qualityProfiles) && qualityProfiles.length > 0; | |||
const hasQualityGate = !!qualityGate; | |||
const shouldShowQualityProfiles = !isView && !isDeveloper && hasQualityProfiles; | |||
const shouldShowQualityGate = !isView && !isDeveloper && hasQualityGate; | |||
@@ -53,11 +53,11 @@ const Meta = ({ component, measures }) => { | |||
<MetaSize component={component} measures={measures}/> | |||
{shouldShowQualityGate && ( | |||
<MetaQualityGate gate={gate}/> | |||
<MetaQualityGate gate={qualityGate}/> | |||
)} | |||
{shouldShowQualityProfiles && ( | |||
<MetaQualityProfiles profiles={profiles}/> | |||
<MetaQualityProfiles profiles={qualityProfiles}/> | |||
)} | |||
<MetaLinks component={component}/> |
@@ -18,12 +18,14 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { getQualityProfileUrl } from '../../../helpers/urls'; | |||
import { searchRules } from '../../../api/rules'; | |||
import { getLanguages } from '../../../app/store/rootReducer'; | |||
export default class MetaQualityProfiles extends React.Component { | |||
class MetaQualityProfiles extends React.Component { | |||
state = { | |||
deprecatedByKey: {} | |||
}; | |||
@@ -69,10 +71,13 @@ export default class MetaQualityProfiles extends React.Component { | |||
} | |||
renderProfile (profile) { | |||
const languageFromStore = this.props.languages[profile.language]; | |||
const languageName = languageFromStore ? languageFromStore.name : profile.language; | |||
const inner = ( | |||
<div className="text-ellipsis"> | |||
<span className="note spacer-right"> | |||
{'(' + profile.language + ')'} | |||
{'(' + languageName + ')'} | |||
</span> | |||
<a href={getQualityProfileUrl(profile.key)}> | |||
{profile.name} | |||
@@ -120,3 +125,9 @@ export default class MetaQualityProfiles extends React.Component { | |||
); | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
languages: getLanguages(state) | |||
}); | |||
export default connect(mapStateToProps)(MetaQualityProfiles); |
@@ -17,25 +17,15 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import App from './App'; | |||
import { getAppState } from '../../../app/store/rootReducer'; | |||
import { getRootQualifiers } from '../../../app/store/appState/duck'; | |||
export default class AppContainer extends React.Component { | |||
state = {}; | |||
const mapStateToProps = state => ({ | |||
topQualifiers: getRootQualifiers(getAppState(state)), | |||
}); | |||
componentDidMount () { | |||
window.sonarqube.appStarted.then(options => { | |||
this.setState({ rootQualifiers: options.rootQualifiers }); | |||
}); | |||
} | |||
render () { | |||
if (!this.state.rootQualifiers) { | |||
return null; | |||
} | |||
return ( | |||
<App {...this.props} topQualifiers={this.state.rootQualifiers}/> | |||
); | |||
} | |||
} | |||
export default connect( | |||
mapStateToProps | |||
)(App); |
@@ -18,14 +18,16 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import PageHeader from './PageHeader'; | |||
import AllHoldersList from './AllHoldersList'; | |||
import PageError from '../../shared/components/PageError'; | |||
import { getComponent, getCurrentUser } from '../../../../app/store/rootReducer'; | |||
import '../../styles.css'; | |||
// TODO helmet | |||
export default class App extends React.Component { | |||
class App extends React.Component { | |||
static propTypes = { | |||
component: React.PropTypes.object | |||
}; | |||
@@ -37,10 +39,18 @@ export default class App extends React.Component { | |||
return ( | |||
<div className="page page-limited"> | |||
<PageHeader project={this.props.component}/> | |||
<PageHeader project={this.props.component} currentUser={this.props.currentUser}/> | |||
<PageError/> | |||
<AllHoldersList project={this.props.component}/> | |||
</div> | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state, ownProps) => ({ | |||
component: getComponent(state, ownProps.location.query.id), | |||
currentUser: getCurrentUser(state) | |||
}); | |||
export default connect(mapStateToProps)(App); | |||
@@ -23,6 +23,7 @@ import { translate } from '../../../../helpers/l10n'; | |||
import ApplyTemplateView from '../views/ApplyTemplateView'; | |||
import { loadHolders } from '../store/actions'; | |||
import { isPermissionsAppLoading } from '../../../../app/store/rootReducer'; | |||
import { isUserAdmin } from '../../../../helpers/users'; | |||
class PageHeader extends React.Component { | |||
static propTypes = { | |||
@@ -59,7 +60,7 @@ class PageHeader extends React.Component { | |||
<i className="spinner"/> | |||
)} | |||
{!!window.SS.isUserAdmin && ( | |||
{isUserAdmin(this.props.currentUser) && ( | |||
<div className="page-actions"> | |||
<button className="js-apply-template" onClick={this.handleApplyTemplate}> | |||
Apply Template |
@@ -18,10 +18,12 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import Header from './Header'; | |||
import Form from './Form'; | |||
import { getComponent } from '../../../app/store/rootReducer'; | |||
export default class Deletion extends React.Component { | |||
class Deletion extends React.Component { | |||
static propTypes = { | |||
component: React.PropTypes.object | |||
}; | |||
@@ -39,3 +41,9 @@ export default class Deletion extends React.Component { | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state, ownProps) => ({ | |||
component: getComponent(state, ownProps.location.query.id) | |||
}); | |||
export default connect(mapStateToProps)(Deletion); |
@@ -35,7 +35,7 @@ import { | |||
import { parseError } from '../../code/utils'; | |||
import { reloadUpdateKeyPage } from './utils'; | |||
import RecentHistory from '../../../app/components/nav/component/RecentHistory'; | |||
import { getProjectAdminProjectModules } from '../../../app/store/rootReducer'; | |||
import { getProjectAdminProjectModules, getComponent } from '../../../app/store/rootReducer'; | |||
class Key extends React.Component { | |||
static propTypes = { | |||
@@ -152,7 +152,8 @@ class Key extends React.Component { | |||
} | |||
const mapStateToProps = (state, ownProps) => ({ | |||
modules: getProjectAdminProjectModules(state, ownProps.component.key) | |||
component: getComponent(state, ownProps.location.query.id), | |||
modules: getProjectAdminProjectModules(state, ownProps.location.query.id) | |||
}); | |||
export default connect( |
@@ -28,7 +28,7 @@ import { | |||
deleteProjectLink, | |||
createProjectLink | |||
} from '../store/actions'; | |||
import { getProjectAdminProjectLinks } from '../../../app/store/rootReducer'; | |||
import { getProjectAdminProjectLinks, getComponent } from '../../../app/store/rootReducer'; | |||
class Links extends React.Component { | |||
static propTypes = { | |||
@@ -73,7 +73,8 @@ class Links extends React.Component { | |||
} | |||
const mapStateToProps = (state, ownProps) => ({ | |||
links: getProjectAdminProjectLinks(state, ownProps.component.key) | |||
component: getComponent(state, ownProps.location.query.id), | |||
links: getProjectAdminProjectLinks(state, ownProps.location.query.id) | |||
}); | |||
export default connect( |
@@ -24,7 +24,7 @@ import Header from './Header'; | |||
import Form from './Form'; | |||
import GlobalMessagesContainer from '../components/GlobalMessagesContainer'; | |||
import { fetchProjectGate, setProjectGate } from '../store/actions'; | |||
import { getProjectAdminAllGates, getProjectAdminProjectGate } from '../../../app/store/rootReducer'; | |||
import { getProjectAdminAllGates, getProjectAdminProjectGate, getComponent } from '../../../app/store/rootReducer'; | |||
class QualityGate extends React.Component { | |||
static propTypes = { | |||
@@ -60,8 +60,9 @@ class QualityGate extends React.Component { | |||
} | |||
const mapStateToProps = (state, ownProps) => ({ | |||
component: getComponent(state, ownProps.location.query.id), | |||
allGates: getProjectAdminAllGates(state), | |||
gate: getProjectAdminProjectGate(state, ownProps.component.key) | |||
gate: getProjectAdminProjectGate(state, ownProps.location.query.id) | |||
}); | |||
export default connect( |
@@ -24,7 +24,11 @@ import Header from './Header'; | |||
import Table from './Table'; | |||
import GlobalMessagesContainer from '../components/GlobalMessagesContainer'; | |||
import { fetchProjectProfiles, setProjectProfile } from '../store/actions'; | |||
import { getProjectAdminAllProfiles, getProjectAdminProjectProfiles } from '../../../app/store/rootReducer'; | |||
import { | |||
getProjectAdminAllProfiles, | |||
getProjectAdminProjectProfiles, | |||
getComponent | |||
} from '../../../app/store/rootReducer'; | |||
class QualityProfiles extends React.Component { | |||
static propTypes = { | |||
@@ -68,8 +72,9 @@ class QualityProfiles extends React.Component { | |||
} | |||
const mapStateToProps = (state, ownProps) => ({ | |||
component: getComponent(state, ownProps.location.query.id), | |||
allProfiles: getProjectAdminAllProfiles(state), | |||
profiles: getProjectAdminProjectProfiles(state, ownProps.component.key) | |||
profiles: getProjectAdminProjectProfiles(state, ownProps.location.query.id) | |||
}); | |||
export default connect( |
@@ -17,36 +17,26 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { connect } from 'react-redux'; | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import Main from './main'; | |||
import { getCurrentUser } from '../../app/store/rootReducer'; | |||
import { getCurrentUser, getAppState } from '../../app/store/rootReducer'; | |||
import { getRootQualifiers } from '../../app/store/appState/duck'; | |||
class AppContainer extends React.Component { | |||
state = {}; | |||
componentDidMount () { | |||
window.sonarqube.appStarted.then(options => { | |||
this.setState({ rootQualifiers: options.rootQualifiers }); | |||
}); | |||
} | |||
render () { | |||
if (!this.props.user || !this.state.rootQualifiers) { | |||
return null; | |||
} | |||
const hasProvisionPermission = this.props.user.permissions.global.indexOf('provisioning') !== -1; | |||
return ( | |||
<Main | |||
hasProvisionPermission={hasProvisionPermission} | |||
topLevelQualifiers={this.state.rootQualifiers}/> | |||
topLevelQualifiers={this.props.rootQualifiers}/> | |||
); | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
rootQualifiers: getRootQualifiers(getAppState(state)), | |||
user: getCurrentUser(state) | |||
}); | |||
@@ -51,10 +51,6 @@ export default class AllProjects extends React.Component { | |||
} | |||
render () { | |||
if (this.props.user == null) { | |||
return null; | |||
} | |||
const isFiltered = Object.keys(this.state.query).some(key => this.state.query[key] != null); | |||
return ( |
@@ -23,7 +23,7 @@ import { translate } from '../../../helpers/l10n'; | |||
export default class FavoriteFilter extends React.Component { | |||
render () { | |||
if (!this.props.user) { | |||
if (!this.props.user.isLoggedIn) { | |||
return null; | |||
} | |||
@@ -19,9 +19,8 @@ | |||
*/ | |||
import React from 'react'; | |||
import { getQualityProfiles, getExporters } from '../../../api/quality-profiles'; | |||
import { getCurrentUser } from '../../../api/users'; | |||
import '../styles.css'; | |||
import { sortProfiles } from '../utils'; | |||
import '../styles.css'; | |||
export default class App extends React.Component { | |||
state = { loading: true }; | |||
@@ -44,16 +43,13 @@ export default class App extends React.Component { | |||
loadData () { | |||
this.setState({ loading: true }); | |||
Promise.all([ | |||
getCurrentUser(), | |||
getExporters(), | |||
getQualityProfiles() | |||
]).then(responses => { | |||
if (this.mounted) { | |||
const [user, exporters, profiles] = responses; | |||
const canAdmin = user.permissions.global.includes('profileadmin'); | |||
const [exporters, profiles] = responses; | |||
this.setState({ | |||
exporters, | |||
canAdmin, | |||
profiles: sortProfiles(profiles), | |||
loading: false | |||
}); | |||
@@ -77,12 +73,14 @@ export default class App extends React.Component { | |||
const finalLanguages = Object.values(this.props.languages); | |||
const canAdmin = this.props.currentUser.permissions.global.includes('profileadmin'); | |||
return React.cloneElement(this.props.children, { | |||
profiles: this.state.profiles, | |||
languages: finalLanguages, | |||
exporters: this.state.exporters, | |||
canAdmin: this.state.canAdmin, | |||
updateProfiles: this.updateProfiles | |||
updateProfiles: this.updateProfiles, | |||
canAdmin | |||
}); | |||
} | |||
@@ -19,10 +19,11 @@ | |||
*/ | |||
import { connect } from 'react-redux'; | |||
import App from './App'; | |||
import { getLanguages } from '../../../app/store/rootReducer'; | |||
import { getLanguages, getCurrentUser } from '../../../app/store/rootReducer'; | |||
export default connect( | |||
state => ({ | |||
currentUser: getCurrentUser(state), | |||
languages: getLanguages(state) | |||
}) | |||
)(App); |
@@ -0,0 +1,102 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default class LoginForm extends React.Component { | |||
static propTypes = { | |||
identityProviders: React.PropTypes.array.isRequired, | |||
onSubmit: React.PropTypes.func.isRequired | |||
}; | |||
state = { | |||
login: '', | |||
password: '' | |||
}; | |||
handleSubmit = (e: any) => { | |||
e.preventDefault(); | |||
this.props.onSubmit(this.state.login, this.state.password); | |||
}; | |||
render () { | |||
return ( | |||
<div> | |||
<h1 className="maintenance-title text-center">Log In to SonarQube</h1> | |||
{this.props.identityProviders.length > 0 && ( | |||
<section className="oauth-providers"> | |||
<ul> | |||
{this.props.identityProviders.map(identityProvider => ( | |||
<li key={identityProvider.key}> | |||
<a href={`${window.baseUrl}/sessions/init/${identityProvider.key}`} | |||
style={{ backgroundColor: identityProvider.backgroundColor }} | |||
title={`Log in with ${identityProvider.name}` }> | |||
<img alt={identityProvider.name} width="20" height="20" | |||
src={window.baseUrl + identityProvider.iconPath}/> | |||
<span>Log in with {identityProvider.name}</span> | |||
</a> | |||
</li> | |||
))} | |||
</ul> | |||
</section> | |||
)} | |||
<form id="login_form" onSubmit={this.handleSubmit}> | |||
<GlobalMessagesContainer/> | |||
<div className="big-spacer-bottom"> | |||
<label htmlFor="login" className="login-label">{translate('login')}</label> | |||
<input type="text" | |||
id="login" | |||
name="login" | |||
className="login-input" | |||
maxLength="255" | |||
required={true} | |||
placeholder={translate('login')} | |||
value={this.state.login} | |||
onChange={e => this.setState({ login: e.target.value })}/> | |||
</div> | |||
<div className="big-spacer-bottom"> | |||
<label htmlFor="password" className="login-label">{translate('password')}</label> | |||
<input type="password" | |||
id="password" | |||
name="password" | |||
className="login-input" | |||
required={true} | |||
placeholder={translate('password')} | |||
value={this.state.password} | |||
onChange={e => this.setState({ password: e.target.value })}/> | |||
</div> | |||
<div> | |||
<div className="text-right overflow-hidden"> | |||
<button name="commit" type="submit">{translate('sessions.log_in')}</button> | |||
<a className="spacer-left" href={window.baseUrl + '/'}>{translate('cancel')}</a> | |||
</div> | |||
</div> | |||
</form> | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,78 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import LoginForm from './LoginForm'; | |||
import { doLogin } from '../../../app/store/rootActions'; | |||
import { getAppState } from '../../../app/store/rootReducer'; | |||
import { getIdentityProviders } from '../../../api/users'; | |||
class LoginFormContainer extends React.Component { | |||
mounted: bool; | |||
static propTypes = { | |||
location: React.PropTypes.object.isRequired | |||
}; | |||
state = {}; | |||
componentDidMount () { | |||
this.mounted = true; | |||
getIdentityProviders().then(r => { | |||
if (this.mounted) { | |||
this.setState({ identityProviders: r.identityProviders }); | |||
} | |||
}); | |||
} | |||
componentWillUnmount () { | |||
this.mounted = false; | |||
} | |||
handleSuccessfulLogin = () => { | |||
window.location = this.props.location.query['return_to'] || (window.baseUrl + '/'); | |||
}; | |||
handleSubmit = (login: string, password: string) => { | |||
this.props.doLogin(login, password).then( | |||
this.handleSuccessfulLogin, | |||
() => { /* do nothing */ } | |||
); | |||
}; | |||
render () { | |||
if (!this.state.identityProviders) { | |||
return null; | |||
} | |||
return ( | |||
<LoginForm identityProviders={this.state.identityProviders} onSubmit={this.handleSubmit}/> | |||
); | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
appState: getAppState(state) | |||
}); | |||
const mapDispatchToProps = { doLogin }; | |||
export default connect(mapStateToProps, mapDispatchToProps)(LoginFormContainer); |
@@ -0,0 +1,42 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer'; | |||
import { doLogout } from '../../../app/store/rootActions'; | |||
class Logout extends React.Component { | |||
componentDidMount () { | |||
this.props.doLogout() | |||
.then(() => window.location = window.baseUrl + '/') | |||
.catch(() => { /* do nothing */ }); | |||
} | |||
render () { | |||
return <GlobalMessagesContainer/>; | |||
} | |||
} | |||
const mapStateToProps = () => ({}); | |||
const mapDispatchToProps = { doLogout }; | |||
export default connect(mapStateToProps, mapDispatchToProps)(Logout); |
@@ -0,0 +1,49 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
export default class Unauthorized extends React.Component { | |||
static propTypes = { | |||
location: React.PropTypes.object.isRequired | |||
}; | |||
render () { | |||
const { message } = this.props.location.query; | |||
return ( | |||
<div className="text-center"> | |||
<p id="unauthorized"> | |||
You're not authorized to access this page. Please contact the administrator. | |||
</p> | |||
{!!message && ( | |||
<p className="spacer-top"> | |||
Reason : {message} | |||
</p> | |||
)} | |||
<div className="big-spacer-top"> | |||
<a href={window.baseUrl + '/'}>Home</a> | |||
</div> | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
/* | |||
* 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 { Route, Redirect } from 'react-router'; | |||
import LoginFormContainer from './components/LoginFormContainer'; | |||
import Logout from './components/Logout'; | |||
import Unauthorized from './components/Unauthorized'; | |||
export default [ | |||
<Redirect key="login" from="/sessions/login" to="/sessions/new"/>, | |||
<Route key="new" path="new" component={LoginFormContainer}/>, | |||
<Route key="logout" path="logout" component={Logout}/>, | |||
<Route key="unauthorized" path="unauthorized" component={Unauthorized}/>, | |||
]; |
@@ -17,24 +17,12 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import App from './App'; | |||
import { getComponent } from '../../../app/store/rootReducer'; | |||
export default class AppContainer extends React.Component { | |||
state = {}; | |||
const mapStateToProps = (state, ownProps) => ({ | |||
component: ownProps.location.query.id ? getComponent(state, ownProps.location.query.id) : undefined | |||
}); | |||
componentDidMount () { | |||
window.sonarqube.appStarted.then(options => | |||
this.setState({ ready: true, component: options.component })); | |||
} | |||
render () { | |||
if (!this.state.ready) { | |||
return null; | |||
} | |||
return ( | |||
<App {...this.props} component={this.state.component}/> | |||
); | |||
} | |||
} | |||
export default connect(mapStateToProps)(App); |
@@ -18,15 +18,17 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { sendTestEmail } from '../../../api/settings'; | |||
import { parseError } from '../../code/utils'; | |||
import { getCurrentUser } from '../../../app/store/rootReducer'; | |||
export default class EmailForm extends React.Component { | |||
class EmailForm extends React.Component { | |||
constructor (props) { | |||
super(props); | |||
this.state = { | |||
recipient: window.SS.userEmail, | |||
recipient: this.props.currentUser.email, | |||
subject: translate('email_configuration.test.subject'), | |||
message: translate('email_configuration.test.message_text'), | |||
loading: false, | |||
@@ -114,3 +116,9 @@ export default class EmailForm extends React.Component { | |||
); | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
currentUser: getCurrentUser(state) | |||
}); | |||
export default connect(mapStateToProps)(EmailForm); |