@@ -22,14 +22,14 @@ package it.ui; | |||
import com.sonar.orchestrator.Orchestrator; | |||
import com.sonar.orchestrator.build.SonarScanner; | |||
import it.Category4Suite; | |||
import it.user.ForceAuthenticationTest; | |||
import java.util.Map; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.ClassRule; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonarqube.ws.client.GetRequest; | |||
import org.sonarqube.ws.client.WsResponse; | |||
import org.sonarqube.ws.client.setting.SetRequest; | |||
import pageobjects.Navigation; | |||
import util.ItUtils; | |||
@@ -40,6 +40,7 @@ import static com.codeborne.selenide.Selenide.$; | |||
import static com.codeborne.selenide.WebDriverRunner.url; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static util.ItUtils.projectDir; | |||
import static util.ItUtils.resetSettings; | |||
import static util.ItUtils.setServerProperty; | |||
public class UiTest { | |||
@@ -50,6 +51,12 @@ public class UiTest { | |||
@Rule | |||
public Navigation nav = Navigation.get(ORCHESTRATOR); | |||
@Before | |||
@After | |||
public void resetData() throws Exception { | |||
resetSettings(ORCHESTRATOR, null, "sonar.forceAuthentication"); | |||
} | |||
@Test | |||
public void footer_contains_information() { | |||
nav.getFooter() | |||
@@ -81,7 +88,6 @@ public class UiTest { | |||
nav.getFooter() | |||
.shouldNot(hasText("About")) | |||
.shouldNot(hasText("Web API")); | |||
setServerProperty(ORCHESTRATOR, "sonar.forceAuthentication", null); | |||
} | |||
@Test |
@@ -26,6 +26,7 @@ import it.Category4Suite; | |||
import java.io.File; | |||
import org.apache.commons.io.FileUtils; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.BeforeClass; | |||
import org.junit.ClassRule; | |||
import org.junit.Test; | |||
@@ -77,12 +78,17 @@ public class BaseIdentityProviderTest { | |||
adminWsClient = newAdminWsClient(ORCHESTRATOR); | |||
} | |||
@Before | |||
@After | |||
public void cleanUpUsersAndGroupsAndProperties() throws Exception { | |||
public void resetData() throws Exception { | |||
userRule.resetUsers(); | |||
userRule.removeGroups(GROUP1, GROUP2, GROUP3); | |||
resetSettings(ORCHESTRATOR, null, "sonar.auth.fake-base-id-provider.enabled", "sonar.auth.fake-base-id-provider.user", | |||
"sonar.auth.fake-base-id-provider.throwUnauthorizedMessage", "sonar.auth.fake-base-id-provider.enabledGroupsSync", "sonar.auth.fake-base-id-provider.groups", | |||
resetSettings(ORCHESTRATOR, null, | |||
"sonar.auth.fake-base-id-provider.enabled", | |||
"sonar.auth.fake-base-id-provider.user", | |||
"sonar.auth.fake-base-id-provider.throwUnauthorizedMessage", | |||
"sonar.auth.fake-base-id-provider.enabledGroupsSync", | |||
"sonar.auth.fake-base-id-provider.groups", | |||
"sonar.auth.fake-base-id-provider.allowsUsersToSignUp"); | |||
} | |||
@@ -70,34 +70,35 @@ public class OAuth2IdentityProviderTest { | |||
String fakeServerAuthProviderUrl; | |||
@BeforeClass | |||
public static void resetData() { | |||
public static void initData() { | |||
ORCHESTRATOR.resetData(); | |||
adminWsClient = newAdminWsClient(ORCHESTRATOR); | |||
} | |||
@After | |||
public void resetUsers() throws Exception { | |||
userRule.resetUsers(); | |||
} | |||
@Before | |||
public void setUp() throws Exception { | |||
fakeServerAuthProvider = new MockWebServer(); | |||
fakeServerAuthProvider.start(); | |||
fakeServerAuthProviderUrl = fakeServerAuthProvider.url("").url().toString(); | |||
userRule.resetUsers(); | |||
resetSettings(ORCHESTRATOR, null, "sonar.auth.fake-oauth2-id-provider.enabled", | |||
"sonar.auth.fake-oauth2-id-provider.url", | |||
"sonar.auth.fake-oauth2-id-provider.user", | |||
"sonar.auth.fake-oauth2-id-provider.throwUnauthorizedMessage", | |||
"sonar.auth.fake-oauth2-id-provider.allowsUsersToSignUp"); | |||
resetData(); | |||
} | |||
@After | |||
public void tearDown() throws Exception { | |||
resetData(); | |||
fakeServerAuthProvider.shutdown(); | |||
} | |||
private void resetData(){ | |||
userRule.resetUsers(); | |||
resetSettings(ORCHESTRATOR, null, | |||
"sonar.auth.fake-oauth2-id-provider.enabled", | |||
"sonar.auth.fake-oauth2-id-provider.url", | |||
"sonar.auth.fake-oauth2-id-provider.user", | |||
"sonar.auth.fake-oauth2-id-provider.throwUnauthorizedMessage", | |||
"sonar.auth.fake-oauth2-id-provider.allowsUsersToSignUp"); | |||
} | |||
@Test | |||
public void create_new_user_when_authenticate() throws Exception { | |||
simulateRedirectionToCallback(); |
@@ -40,7 +40,8 @@ type Props = { | |||
fetchMyOrganizations: () => Promise<*>, | |||
location: Object, | |||
organizations: Array<{ key: string, name: string }>, | |||
router: { push: string => void } | |||
router: { push: string => void }, | |||
sonarCloud: boolean | |||
}; | |||
type State = { | |||
@@ -156,11 +157,24 @@ export default class GlobalNavUser extends React.PureComponent { | |||
} | |||
renderAnonymous() { | |||
return ( | |||
<li> | |||
<a onClick={this.handleLogin} href="#">{translate('layout.login')}</a> | |||
</li> | |||
); | |||
return this.props.sonarCloud | |||
? <li> | |||
<a href="/sessions/init/github"> | |||
<img | |||
alt="GitHub" | |||
className="navbar-global-login-github" | |||
width="14" | |||
height="14" | |||
src="/static/authgithub/github.svg" | |||
/> | |||
{translate('layout.login')} | |||
</a> | |||
</li> | |||
: <li> | |||
<a onClick={this.handleLogin} href="#"> | |||
{translate('layout.login')} | |||
</a> | |||
</li>; | |||
} | |||
render() { |
@@ -125,6 +125,7 @@ const startReactApp = () => { | |||
<Redirect from="/profiles/index" to="/profiles" /> | |||
<Redirect from="/quality_gates/index" to="/quality_gates" /> | |||
<Redirect from="/settings/index" to="/settings" /> | |||
<Redirect from="/sessions/login" to="/sessions/new" /> | |||
<Redirect from="/system/index" to="/system" /> | |||
<Route path="markdown/help" component={MarkdownHelp} /> | |||
@@ -138,7 +139,7 @@ const startReactApp = () => { | |||
<Route component={MigrationContainer}> | |||
<Route component={SimpleSessionsContainer}> | |||
<Route path="/sessions">{sessionsRoutes}</Route> | |||
<Route path="/sessions" childRoutes={sessionsRoutes} /> | |||
</Route> | |||
<Route path="/" component={App}> |
@@ -19,7 +19,6 @@ | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { Link } from 'react-router'; | |||
import AboutProjects from './AboutProjects'; | |||
import EntryIssueTypesForSonarQubeDotCom from './EntryIssueTypesForSonarQubeDotCom'; | |||
import AboutRulesForSonarQubeDotCom from './AboutRulesForSonarQubeDotCom'; | |||
@@ -29,7 +28,6 @@ import AboutQualityGates from './AboutQualityGates'; | |||
import AboutLeakPeriod from './AboutLeakPeriod'; | |||
import AboutStandards from './AboutStandards'; | |||
import AboutScanners from './AboutScanners'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import '../sonarqube-dot-com-styles.css'; | |||
type Props = { | |||
@@ -57,16 +55,11 @@ export default function AboutAppForSonarQubeDotCom(props: Props) { | |||
<h1 className="big-spacer-bottom"> | |||
Continuous Code Quality<br />as a Service | |||
</h1> | |||
<a | |||
className="button button-active" | |||
href="https://about.sonarcloud.io/get-started/" | |||
target="_blank"> | |||
Get Started | |||
</a> | |||
{!props.currentUser.isLoggedIn && | |||
<Link to="/sessions/new" className="button big-spacer-left"> | |||
{translate('layout.login')} | |||
</Link>} | |||
<a className="sonarcloud-about-github-button" href="/sessions/init/github"> | |||
<img alt="GitHub" width="20" height="20" src="/static/authgithub/github.svg" /> | |||
Connect With GitHub to Get Started | |||
</a>} | |||
</div> | |||
<div className="sqcom-about-page-instance"> |
@@ -31,35 +31,29 @@ | |||
font-weight: 300; | |||
} | |||
.sqcom-about-page-intro > .button { | |||
.sonarcloud-about-github-button { | |||
display: inline-block; | |||
height: 44px; | |||
line-height: 42px; | |||
line-height: 46px; | |||
padding-left: 20px; | |||
padding-right: 20px; | |||
border-color: #fff; | |||
border: none; | |||
border-radius: 3px; | |||
color: #fff; | |||
font-size: 16px; | |||
background-color: #444; | |||
color: #fff !important; | |||
font-size: 15px; | |||
font-weight: 500; | |||
text-transform: uppercase; | |||
transition: none; | |||
} | |||
.sqcom-about-page-intro > .button:hover { | |||
background-color: #fff; | |||
color: #4b9fd5; | |||
} | |||
.sqcom-about-page-intro > .button-active { | |||
border-color: #b0eb41; | |||
background-color: #b0eb41; | |||
color: #225463; | |||
.sonarcloud-about-github-button:hover, | |||
.sonarcloud-about-github-button:focus { | |||
background-color: #333; | |||
} | |||
.sqcom-about-page-intro > .button-active:hover { | |||
border-color: #91d315; | |||
background-color: #91d315; | |||
color: #225463; | |||
.sonarcloud-about-github-button img { | |||
margin-top: 12px; | |||
margin-right: 10px; | |||
} | |||
.sqcom-about-page-instance { |
@@ -22,26 +22,49 @@ import React from 'react'; | |||
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer'; | |||
import { translate } from '../../../helpers/l10n'; | |||
type Props = { | |||
identityProviders: Array<{ | |||
backgroundColor: string, | |||
iconPath: string, | |||
key: string, | |||
name: string | |||
}>, | |||
onSubmit: (string, string) => void | |||
}; | |||
type State = { | |||
collapsed: boolean, | |||
login: string, | |||
password: string | |||
}; | |||
export default class LoginForm extends React.PureComponent { | |||
static propTypes = { | |||
identityProviders: React.PropTypes.array.isRequired, | |||
onSubmit: React.PropTypes.func.isRequired | |||
}; | |||
props: Props; | |||
state: State; | |||
state = { | |||
login: '', | |||
password: '' | |||
}; | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { | |||
collapsed: props.identityProviders.length > 0, | |||
login: '', | |||
password: '' | |||
}; | |||
} | |||
handleSubmit = (e: Object) => { | |||
e.preventDefault(); | |||
handleSubmit = (event: Event) => { | |||
event.preventDefault(); | |||
this.props.onSubmit(this.state.login, this.state.password); | |||
}; | |||
handleMoreOptionsClick = (event: Event) => { | |||
event.preventDefault(); | |||
this.setState({ collapsed: false }); | |||
}; | |||
render() { | |||
return ( | |||
<div> | |||
<h1 className="maintenance-title text-center">Log In to SonarQube</h1> | |||
<div id="login_form"> | |||
<h1 className="maintenance-title text-center">{translate('login.login_to_sonarqube')}</h1> | |||
{this.props.identityProviders.length > 0 && | |||
<section className="oauth-providers"> | |||
@@ -65,46 +88,55 @@ export default class LoginForm extends React.PureComponent { | |||
</ul> | |||
</section>} | |||
<form id="login_form" onSubmit={this.handleSubmit}> | |||
<GlobalMessagesContainer /> | |||
{this.state.collapsed | |||
? <div className="text-center"> | |||
<a | |||
className="small text-muted js-more-options" | |||
href="#" | |||
onClick={this.handleMoreOptionsClick}> | |||
{translate('login.more_options')} | |||
</a> | |||
</div> | |||
: <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} | |||
autoFocus={true} | |||
placeholder={translate('login')} | |||
value={this.state.login} | |||
onChange={e => this.setState({ login: e.target.value })} | |||
/> | |||
</div> | |||
<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} | |||
autoFocus={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 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> | |||
<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,60 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 SonarSource SA | |||
* mailto:info 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 { shallow } from 'enzyme'; | |||
import LoginForm from '../LoginForm'; | |||
import { change, click, submit } from '../../../../helpers/testUtils'; | |||
const identityProvider = { | |||
backgroundColor: '#000', | |||
iconPath: '/some/path', | |||
key: 'foo', | |||
name: 'foo' | |||
}; | |||
it('logs in with simple credentials', () => { | |||
const onSubmit = jest.fn(); | |||
const wrapper = shallow(<LoginForm identityProviders={[]} onSubmit={onSubmit} />); | |||
expect(wrapper).toMatchSnapshot(); | |||
change(wrapper.find('#login'), 'admin'); | |||
change(wrapper.find('#password'), 'admin'); | |||
submit(wrapper.find('form')); | |||
expect(onSubmit).toBeCalledWith('admin', 'admin'); | |||
}); | |||
it('logs in with identity provider', () => { | |||
const wrapper = shallow( | |||
<LoginForm identityProviders={[identityProvider]} onSubmit={jest.fn()} /> | |||
); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('expands more options', () => { | |||
const wrapper = shallow( | |||
<LoginForm identityProviders={[identityProvider]} onSubmit={jest.fn()} /> | |||
); | |||
expect(wrapper).toMatchSnapshot(); | |||
click(wrapper.find('.js-more-options')); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,285 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`expands more options 1`] = ` | |||
<div | |||
id="login_form" | |||
> | |||
<h1 | |||
className="maintenance-title text-center" | |||
> | |||
login.login_to_sonarqube | |||
</h1> | |||
<section | |||
className="oauth-providers" | |||
> | |||
<ul> | |||
<li> | |||
<a | |||
href="/sessions/init/foo" | |||
style={ | |||
Object { | |||
"backgroundColor": "#000", | |||
} | |||
} | |||
title="Log in with foo" | |||
> | |||
<img | |||
alt="foo" | |||
height="20" | |||
src="/some/path" | |||
width="20" | |||
/> | |||
<span> | |||
Log in with | |||
foo | |||
</span> | |||
</a> | |||
</li> | |||
</ul> | |||
</section> | |||
<div | |||
className="text-center" | |||
> | |||
<a | |||
className="small text-muted js-more-options" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
login.more_options | |||
</a> | |||
</div> | |||
</div> | |||
`; | |||
exports[`expands more options 2`] = ` | |||
<div | |||
id="login_form" | |||
> | |||
<h1 | |||
className="maintenance-title text-center" | |||
> | |||
login.login_to_sonarqube | |||
</h1> | |||
<section | |||
className="oauth-providers" | |||
> | |||
<ul> | |||
<li> | |||
<a | |||
href="/sessions/init/foo" | |||
style={ | |||
Object { | |||
"backgroundColor": "#000", | |||
} | |||
} | |||
title="Log in with foo" | |||
> | |||
<img | |||
alt="foo" | |||
height="20" | |||
src="/some/path" | |||
width="20" | |||
/> | |||
<span> | |||
Log in with | |||
foo | |||
</span> | |||
</a> | |||
</li> | |||
</ul> | |||
</section> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<Connect(GlobalMessages) /> | |||
<div | |||
className="big-spacer-bottom" | |||
> | |||
<label | |||
className="login-label" | |||
htmlFor="login" | |||
> | |||
login | |||
</label> | |||
<input | |||
autoFocus={true} | |||
className="login-input" | |||
id="login" | |||
maxLength="255" | |||
name="login" | |||
onChange={[Function]} | |||
placeholder="login" | |||
required={true} | |||
type="text" | |||
value="" | |||
/> | |||
</div> | |||
<div | |||
className="big-spacer-bottom" | |||
> | |||
<label | |||
className="login-label" | |||
htmlFor="password" | |||
> | |||
password | |||
</label> | |||
<input | |||
className="login-input" | |||
id="password" | |||
name="password" | |||
onChange={[Function]} | |||
placeholder="password" | |||
required={true} | |||
type="password" | |||
value="" | |||
/> | |||
</div> | |||
<div> | |||
<div | |||
className="text-right overflow-hidden" | |||
> | |||
<button | |||
name="commit" | |||
type="submit" | |||
> | |||
sessions.log_in | |||
</button> | |||
<a | |||
className="spacer-left" | |||
href="/" | |||
> | |||
cancel | |||
</a> | |||
</div> | |||
</div> | |||
</form> | |||
</div> | |||
`; | |||
exports[`logs in with identity provider 1`] = ` | |||
<div | |||
id="login_form" | |||
> | |||
<h1 | |||
className="maintenance-title text-center" | |||
> | |||
login.login_to_sonarqube | |||
</h1> | |||
<section | |||
className="oauth-providers" | |||
> | |||
<ul> | |||
<li> | |||
<a | |||
href="/sessions/init/foo" | |||
style={ | |||
Object { | |||
"backgroundColor": "#000", | |||
} | |||
} | |||
title="Log in with foo" | |||
> | |||
<img | |||
alt="foo" | |||
height="20" | |||
src="/some/path" | |||
width="20" | |||
/> | |||
<span> | |||
Log in with | |||
foo | |||
</span> | |||
</a> | |||
</li> | |||
</ul> | |||
</section> | |||
<div | |||
className="text-center" | |||
> | |||
<a | |||
className="small text-muted js-more-options" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
login.more_options | |||
</a> | |||
</div> | |||
</div> | |||
`; | |||
exports[`logs in with simple credentials 1`] = ` | |||
<div | |||
id="login_form" | |||
> | |||
<h1 | |||
className="maintenance-title text-center" | |||
> | |||
login.login_to_sonarqube | |||
</h1> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<Connect(GlobalMessages) /> | |||
<div | |||
className="big-spacer-bottom" | |||
> | |||
<label | |||
className="login-label" | |||
htmlFor="login" | |||
> | |||
login | |||
</label> | |||
<input | |||
autoFocus={true} | |||
className="login-input" | |||
id="login" | |||
maxLength="255" | |||
name="login" | |||
onChange={[Function]} | |||
placeholder="login" | |||
required={true} | |||
type="text" | |||
value="" | |||
/> | |||
</div> | |||
<div | |||
className="big-spacer-bottom" | |||
> | |||
<label | |||
className="login-label" | |||
htmlFor="password" | |||
> | |||
password | |||
</label> | |||
<input | |||
className="login-input" | |||
id="password" | |||
name="password" | |||
onChange={[Function]} | |||
placeholder="password" | |||
required={true} | |||
type="password" | |||
value="" | |||
/> | |||
</div> | |||
<div> | |||
<div | |||
className="text-right overflow-hidden" | |||
> | |||
<button | |||
name="commit" | |||
type="submit" | |||
> | |||
sessions.log_in | |||
</button> | |||
<a | |||
className="spacer-left" | |||
href="/" | |||
> | |||
cancel | |||
</a> | |||
</div> | |||
</div> | |||
</form> | |||
</div> | |||
`; |
@@ -17,15 +17,31 @@ | |||
* 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} /> | |||
const routes = [ | |||
{ | |||
path: 'new', | |||
getComponent(_, callback) { | |||
require.ensure([], require => { | |||
callback(null, require('./components/LoginFormContainer').default); | |||
}); | |||
} | |||
}, | |||
{ | |||
path: 'logout', | |||
getComponent(_, callback) { | |||
require.ensure([], require => { | |||
callback(null, require('./components/Logout').default); | |||
}); | |||
} | |||
}, | |||
{ | |||
path: 'unauthorized', | |||
getComponent(_, callback) { | |||
require.ensure([], require => { | |||
callback(null, require('./components/Unauthorized').default); | |||
}); | |||
} | |||
} | |||
]; | |||
export default routes; |
@@ -267,6 +267,11 @@ | |||
} | |||
} | |||
.navbar-global-login-github { | |||
margin-top: 3px; | |||
margin-right: 4px; | |||
} | |||
.navbar-context { | |||
position: static; |
@@ -70,9 +70,6 @@ | |||
} | |||
.oauth-providers { | |||
margin-bottom: 30px; | |||
border-bottom: 1px solid @barBorderColor; | |||
& > ul { | |||
display: flex; | |||
justify-content: space-around; | |||
@@ -106,3 +103,8 @@ | |||
} | |||
} | |||
} | |||
.oauth-providers + form { | |||
padding-top: 30px; | |||
border-top: 1px solid @barBorderColor; | |||
} |
@@ -1841,6 +1841,9 @@ user.scm_account_already_used=The scm account '{0}' is already used by user(s) : | |||
user.login_or_email_used_as_scm_account=Login and email are automatically considered as SCM accounts | |||
user.password_cant_be_changed_on_external_auth=Password cannot be changed when external authentication is used | |||
login.login_to_sonarqube=Log In to SonarQube | |||
login.more_options=More options | |||
#------------------------------------------------------------------------------ | |||
# | |||
# USERS PAGE |