]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9357 Hide default login form if there are other ways to login (#2163)
authorStas Vilchik <stas.vilchik@sonarsource.com>
Tue, 13 Jun 2017 12:14:16 +0000 (05:14 -0700)
committerStas Vilchik <stas.vilchik@sonarsource.com>
Tue, 20 Jun 2017 11:10:53 +0000 (04:10 -0700)
14 files changed:
it/it-tests/src/test/java/it/ui/UiTest.java
it/it-tests/src/test/java/it/user/BaseIdentityProviderTest.java
it/it-tests/src/test/java/it/user/OAuth2IdentityProviderTest.java
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js
server/sonar-web/src/main/js/app/utils/startReactApp.js
server/sonar-web/src/main/js/apps/about/components/AboutAppForSonarQubeDotCom.js
server/sonar-web/src/main/js/apps/about/sonarqube-dot-com-styles.css
server/sonar-web/src/main/js/apps/sessions/components/LoginForm.js
server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginForm-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.js.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/sessions/routes.js
server/sonar-web/src/main/less/components/navbar.less
server/sonar-web/src/main/less/pages/login.less
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 9c002a029a1608100a5d7c66500d3acf7f0bf308..6321cee2e85c66b2e3c1231f3ea2cbd024e594c1 100644 (file)
@@ -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
index 36dc82ec7a93c1a9b906068adbe7ede55a1ed2ca..69213444b2a398815ed748380d5cbf6433c0aac6 100644 (file)
@@ -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");
   }
 
index fa91d6cfefba9b50b0fa6ceb69d13af60832f29e..d202a6b8402aec4a4db0aa7950134f4b590bb779 100644 (file)
@@ -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();
index 707e372a8eafb660b25236e06f9159847e65c41e..a4e39e093983281591527a4dea857d440be9d247 100644 (file)
@@ -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() {
index f0793da6798f946c3a9dee31f51c176ac290a954..1ffac0eb63666b0a2bd0ebeb3f525aefd137f087 100644 (file)
@@ -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}>
index 91b533dd30719bdc3b6fc1c30f65d1d34b17afa1..6072a5afb54f2a94e666791526953ab24b543b63 100644 (file)
@@ -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">
index 693e4ee39082812715ea2ef8ea3addf97312461f..1895646e681e67c7467370ea87abf2573f4c11c8 100644 (file)
   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 {
index 06abd2f82c9a445765998d5ae433b0803222a571..3ac61372fdf86cf24ad10fd9e75865a02f132b38 100644 (file)
@@ -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>
     );
   }
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginForm-test.js b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/LoginForm-test.js
new file mode 100644 (file)
index 0000000..6eea812
--- /dev/null
@@ -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();
+});
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.js.snap b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.js.snap
new file mode 100644 (file)
index 0000000..58c919a
--- /dev/null
@@ -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>
+`;
index 5909cbb53fde42adab3b2d0e059f7e19deec06be..957fb2a72072201b7950456b5b87ef3077dcf17d 100644 (file)
  * 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;
index fd7ac938e48f3cccdf2202a45179e92b23b15742..303d4b3cd783430e23313cb240654185ac1a0c5e 100644 (file)
   }
 }
 
+.navbar-global-login-github {
+  margin-top: 3px;
+  margin-right: 4px;
+}
+
 
 .navbar-context {
   position: static;
index ce54c81cf7cdd5f8f6becbb6f1b12bcc0cfcb6ce..991fa4383b36e4b531e29873d98f9ca2e4384b77 100644 (file)
@@ -70,9 +70,6 @@
 }
 
 .oauth-providers {
-  margin-bottom: 30px;
-  border-bottom: 1px solid @barBorderColor;
-
   & > ul {
     display: flex;
     justify-content: space-around;
     }
   }
 }
+
+.oauth-providers + form {
+  padding-top: 30px;
+  border-top: 1px solid @barBorderColor;
+}
index b64d272115811240dda41575873a050b6d13c4e1..ee0c39066aacbffa64baef329ff809d4457a0956 100644 (file)
@@ -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