Browse Source

SONAR-10289 Update login page UI and allow to display identity providers help

tags/7.5
Stas Vilchik 6 years ago
parent
commit
883828c4e9
34 changed files with 349 additions and 247 deletions
  1. 1
    22
      server/sonar-web/src/main/js/api/users.ts
  2. 11
    5
      server/sonar-web/src/main/js/app/components/NotFound.js
  3. 1
    6
      server/sonar-web/src/main/js/app/components/SimpleContainer.tsx
  4. 4
    1
      server/sonar-web/src/main/js/app/styles/components/ui.css
  5. 22
    0
      server/sonar-web/src/main/js/app/types.ts
  6. 7
    1
      server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.js
  7. 0
    6
      server/sonar-web/src/main/js/apps/maintenance/main-view.js
  8. 24
    18
      server/sonar-web/src/main/js/apps/maintenance/templates/maintenance-main.hbs
  9. 42
    34
      server/sonar-web/src/main/js/apps/sessions/components/EmailAlreadyExists.tsx
  10. 10
    0
      server/sonar-web/src/main/js/apps/sessions/components/LoginForm.css
  11. 3
    3
      server/sonar-web/src/main/js/apps/sessions/components/LoginForm.tsx
  12. 2
    1
      server/sonar-web/src/main/js/apps/sessions/components/LoginFormContainer.tsx
  13. 2
    2
      server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx
  14. 13
    7
      server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.css
  15. 17
    6
      server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.tsx
  16. 13
    9
      server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.tsx
  17. 97
    87
      server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/EmailAlreadyExists-test.tsx.snap
  18. 6
    0
      server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.tsx.snap
  19. 2
    2
      server/sonar-web/src/main/js/apps/users/UsersApp.tsx
  20. 1
    1
      server/sonar-web/src/main/js/apps/users/UsersList.tsx
  21. 0
    0
      server/sonar-web/src/main/js/apps/users/__tests__/UsersList-test.tsx
  22. 0
    0
      server/sonar-web/src/main/js/apps/users/__tests__/__snapshots__/UsersList-test.tsx.snap
  23. 2
    1
      server/sonar-web/src/main/js/apps/users/components/DeactivateForm.tsx
  24. 1
    1
      server/sonar-web/src/main/js/apps/users/components/GroupsForm.tsx
  25. 2
    1
      server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx
  26. 1
    1
      server/sonar-web/src/main/js/apps/users/components/TokensFormModal.tsx
  27. 1
    1
      server/sonar-web/src/main/js/apps/users/components/UserActions.tsx
  28. 2
    1
      server/sonar-web/src/main/js/apps/users/components/UserForm.tsx
  29. 1
    1
      server/sonar-web/src/main/js/apps/users/components/UserGroups.tsx
  30. 1
    1
      server/sonar-web/src/main/js/apps/users/components/UserListItem.tsx
  31. 8
    2
      server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx
  32. 1
    23
      server/sonar-web/src/main/js/components/ui/GenericAvatar.tsx
  33. 48
    0
      server/sonar-web/src/main/js/helpers/colors.ts
  34. 3
    3
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 1
- 22
server/sonar-web/src/main/js/api/users.ts View File

@@ -19,28 +19,7 @@
*/
import { getJSON, post, postJSON, RequestData } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
import { Paging, HomePage, CurrentUser } from '../app/types';

export interface IdentityProvider {
backgroundColor: string;
iconPath: string;
key: string;
name: string;
}

export interface User {
login: string;
name: string;
active: boolean;
email?: string;
scmAccounts?: string[];
groups?: string[];
tokensCount?: number;
local: boolean;
externalIdentity?: string;
externalProvider?: string;
avatar?: string;
}
import { Paging, HomePage, CurrentUser, IdentityProvider, User } from '../app/types';

export function getCurrentUser(): Promise<CurrentUser> {
return getJSON('/api/users/current');

+ 11
- 5
server/sonar-web/src/main/js/app/components/NotFound.js View File

@@ -24,11 +24,17 @@ import SimpleContainer from './SimpleContainer';
export default function NotFound() {
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>
<Link to="/">Go back to the homepage</Link>
</p>
<div id="bd" className="page-wrapper-simple">
<div id="nonav" className="page-simple">
<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>
<Link to="/">Go back to the homepage</Link>
</p>
</div>
</div>
</SimpleContainer>
);
}

+ 1
- 6
server/sonar-web/src/main/js/app/components/SimpleContainer.tsx View File

@@ -77,12 +77,7 @@ export default class SimpleContainer extends React.PureComponent<Props, State> {
<div className="global-container">
<div className="page-wrapper" id="container">
<NavBar className="navbar-global" height={theme.globalNavHeightRaw} />

<div id="bd" className="page-wrapper-simple">
<div id="nonav" className="page-simple">
{this.props.children}
</div>
</div>
{this.props.children}
</div>
<GlobalFooterContainer hideLoggedInInfo={this.props.hideLoggedInInfo} />
</div>

+ 4
- 1
server/sonar-web/src/main/js/app/styles/components/ui.css View File

@@ -122,8 +122,11 @@
.identity-provider {
display: inline-block;
line-height: 14px;
padding: 3px 5px;
padding: 2px 5px;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 3px;
box-sizing: border-box;
background-color: var(--darkBlue);
font-size: var(--smallFontSize);
color: #fff;
}

+ 22
- 0
server/sonar-web/src/main/js/app/types.ts View File

@@ -269,3 +269,25 @@ export enum RuleScope {
Test = 'TEST',
All = 'ALL'
}

export interface IdentityProvider {
backgroundColor: string;
helpMessage?: string;
iconPath: string;
key: string;
name: string;
}

export interface User {
active: boolean;
avatar?: string;
email?: string;
externalIdentity?: string;
externalProvider?: string;
groups?: string[];
local: boolean;
login: string;
name: string;
scmAccounts?: string[];
tokensCount?: number;
}

+ 7
- 1
server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.js View File

@@ -19,6 +19,8 @@
*/
import React from 'react';
import { getIdentityProviders } from '../../../api/users';
import * as theme from '../../../app/theme';
import { getTextColor } from '../../../helpers/colors';

export default class UserExternalIdentity extends React.PureComponent {
state = {
@@ -80,8 +82,12 @@ export default class UserExternalIdentity extends React.PureComponent {
return (
<div
className="identity-provider"
style={{ backgroundColor: identityProvider.backgroundColor }}>
style={{
backgroundColor: identityProvider.backgroundColor,
color: getTextColor(identityProvider.backgroundColor, theme.secondFontColor)
}}>
<img
className="little-spacer-right"
src={window.baseUrl + identityProvider.iconPath}
width="14"
height="14"

+ 0
- 6
server/sonar-web/src/main/js/apps/maintenance/main-view.js View File

@@ -80,12 +80,6 @@ export default Marionette.ItemView.extend({
);
},

onRender() {
document
.querySelector('.page-simple')
.classList.toggle('panel-warning', this.model.get('state') === 'MIGRATION_REQUIRED');
},

loadPreviousPage() {
setInterval(() => {
window.location = this.options.returnTo || getBaseUrl();

+ 24
- 18
server/sonar-web/src/main/js/apps/maintenance/templates/maintenance-main.hbs View File

@@ -1,26 +1,32 @@
{{#eq status 'OFFLINE'}}
<div id="bd" class="page-wrapper-simple">
<div id="nonav" class="page-simple {{#eq state 'MIGRATION_REQUIRED'}}panel-warning{{/eq}}">

{{> '_maintenance-status-offline'}}
{{#eq status 'OFFLINE'}}

{{else}}
{{> '_maintenance-status-offline'}}

{{#unless setup}}
{{else}}

{{#eq status 'UP'}}{{> '_maintenance-status-up'}}{{/eq}}
{{#eq status 'STARTING'}}{{> '_maintenance-status-starting'}}{{/eq}}
{{#eq status 'DOWN'}}{{> '_maintenance-status-down'}}{{/eq}}
{{#eq status 'DB_MIGRATION_NEEDED'}}{{> '_maintenance-status-migration'}}{{/eq}}
{{#eq status 'DB_MIGRATION_RUNNING'}}{{> '_maintenance-status-migration'}}{{/eq}}
{{#unless setup}}

{{else}}
{{#eq status 'UP'}}{{> '_maintenance-status-up'}}{{/eq}}
{{#eq status 'STARTING'}}{{> '_maintenance-status-starting'}}{{/eq}}
{{#eq status 'DOWN'}}{{> '_maintenance-status-down'}}{{/eq}}
{{#eq status 'DB_MIGRATION_NEEDED'}}{{> '_maintenance-status-migration'}}{{/eq}}
{{#eq status 'DB_MIGRATION_RUNNING'}}{{> '_maintenance-status-migration'}}{{/eq}}

{{#eq state 'NO_MIGRATION'}}{{> '_maintenance-state-no-migration'}}{{/eq}}
{{#eq state 'MIGRATION_REQUIRED'}}{{> '_maintenance-state-migration-required'}}{{/eq}}
{{#eq state 'NOT_SUPPORTED'}}{{> '_maintenance-state-migration-not-supported'}}{{/eq}}
{{#eq state 'MIGRATION_RUNNING'}}{{> '_maintenance-state-migration-running'}}{{/eq}}
{{#eq state 'MIGRATION_SUCCEEDED'}}{{> '_maintenance-state-migration-succeeded'}}{{/eq}}
{{#eq state 'MIGRATION_FAILED'}}{{> '_maintenance-state-migration-failed'}}{{/eq}}
{{else}}

{{/unless}}
{{#eq state 'NO_MIGRATION'}}{{> '_maintenance-state-no-migration'}}{{/eq}}
{{#eq state 'MIGRATION_REQUIRED'}}{{> '_maintenance-state-migration-required'}}{{/eq}}
{{#eq state 'NOT_SUPPORTED'}}{{> '_maintenance-state-migration-not-supported'}}{{/eq}}
{{#eq state 'MIGRATION_RUNNING'}}{{> '_maintenance-state-migration-running'}}{{/eq}}
{{#eq state 'MIGRATION_SUCCEEDED'}}{{> '_maintenance-state-migration-succeeded'}}{{/eq}}
{{#eq state 'MIGRATION_FAILED'}}{{> '_maintenance-state-migration-failed'}}{{/eq}}

{{/eq}}
{{/unless}}

{{/eq}}

</div>
</div>

+ 42
- 34
server/sonar-web/src/main/js/apps/sessions/components/EmailAlreadyExists.tsx View File

@@ -19,7 +19,10 @@
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { getIdentityProviders, IdentityProvider } from '../../../api/users';
import { getIdentityProviders } from '../../../api/users';
import * as theme from '../../../app/theme';
import { IdentityProvider } from '../../../app/types';
import { getTextColor } from '../../../helpers/colors';
import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/urls';

@@ -75,7 +78,10 @@ export default class EmailAlreadyExists extends React.PureComponent<Props, State
return identityProvider ? (
<div
className="identity-provider"
style={{ backgroundColor: identityProvider.backgroundColor }}>
style={{
backgroundColor: identityProvider.backgroundColor,
color: getTextColor(identityProvider.backgroundColor, theme.secondFontColor)
}}>
<img
alt={identityProvider.name}
className="little-spacer-right"
@@ -96,41 +102,43 @@ export default class EmailAlreadyExists extends React.PureComponent<Props, State
const { query } = this.props.location;

return (
<div>
<div className="big-spacer-bottom js-existing-account">
<p className="little-spacer-bottom">
<FormattedMessage
defaultMessage={translate('sessions.email_already_exists.1')}
id="sessions.email_already_exists.1"
values={{ email: <strong>{query.email}</strong> }}
/>
</p>
{this.renderIdentityProvier(query.existingProvider, query.existingLogin)}
</div>
<div id="bd" className="page-wrapper-simple">
<div id="nonav" className="page-simple">
<div className="big-spacer-bottom js-existing-account">
<p className="little-spacer-bottom">
<FormattedMessage
defaultMessage={translate('sessions.email_already_exists.1')}
id="sessions.email_already_exists.1"
values={{ email: <strong>{query.email}</strong> }}
/>
</p>
{this.renderIdentityProvier(query.existingProvider, query.existingLogin)}
</div>

<div className="big-spacer-bottom js-new-account">
<p className="little-spacer-bottom">{translate('sessions.email_already_exists.2')}</p>
{this.renderIdentityProvier(query.provider, query.login)}
</div>
<div className="big-spacer-bottom js-new-account">
<p className="little-spacer-bottom">{translate('sessions.email_already_exists.2')}</p>
{this.renderIdentityProvier(query.provider, query.login)}
</div>

<div className="alert alert-warning">
{translate('sessions.email_already_exists.3')}
<ul className="list-styled">
<li className="spacer-top">{translate('sessions.email_already_exists.4')}</li>
<li className="spacer-top">{translate('sessions.email_already_exists.5')}</li>
<li className="spacer-top">{translate('sessions.email_already_exists.6')}</li>
</ul>
</div>
<div className="alert alert-warning">
{translate('sessions.email_already_exists.3')}
<ul className="list-styled">
<li className="spacer-top">{translate('sessions.email_already_exists.4')}</li>
<li className="spacer-top">{translate('sessions.email_already_exists.5')}</li>
<li className="spacer-top">{translate('sessions.email_already_exists.6')}</li>
</ul>
</div>

<div className="big-spacer-top text-right">
<a
className="button js-continue"
href={`${getBaseUrl()}/sessions/init/${query.provider}?allowEmailShift=true`}>
{translate('continue')}
</a>
<a className="big-spacer-left js-cancel" href={getBaseUrl() + '/'}>
{translate('cancel')}
</a>
<div className="big-spacer-top text-right">
<a
className="button js-continue"
href={`${getBaseUrl()}/sessions/init/${query.provider}?allowEmailShift=true`}>
{translate('continue')}
</a>
<a className="big-spacer-left js-cancel" href={getBaseUrl() + '/'}>
{translate('cancel')}
</a>
</div>
</div>
</div>
);

+ 10
- 0
server/sonar-web/src/main/js/apps/sessions/components/LoginForm.css View File

@@ -17,6 +17,16 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
.login-page {
padding-top: 10vh;
}

.login-form {
width: 300px;
margin-left: auto;
margin-right: auto;
}

.login-title {
margin-bottom: 40px;
line-height: 1.5;

+ 3
- 3
server/sonar-web/src/main/js/apps/sessions/components/LoginForm.tsx View File

@@ -21,8 +21,8 @@ import * as React from 'react';
import { Link } from 'react-router';
import OAuthProviders from './OAuthProviders';
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer';
import { IdentityProvider } from '../../../app/types';
import { translate } from '../../../helpers/l10n';
import { IdentityProvider } from '../../../api/users';
import './LoginForm.css';

interface Props {
@@ -70,7 +70,7 @@ export default class LoginForm extends React.PureComponent<Props, State> {
: translate('login.login_to_sonarqube');

return (
<div id="login_form">
<div className="login-page" id="login_form">
<h1 className="login-title text-center">{loginTitle}</h1>

{this.props.identityProviders.length > 0 && (
@@ -90,7 +90,7 @@ export default class LoginForm extends React.PureComponent<Props, State> {
</a>
</div>
) : (
<form onSubmit={this.handleSubmit}>
<form className="login-form" onSubmit={this.handleSubmit}>
<GlobalMessagesContainer />

<div className="big-spacer-bottom">

+ 2
- 1
server/sonar-web/src/main/js/apps/sessions/components/LoginFormContainer.tsx View File

@@ -22,7 +22,8 @@ import * as PropTypes from 'prop-types';
import { connect } from 'react-redux';
import LoginForm from './LoginForm';
import { doLogin } from '../../../store/rootActions';
import { IdentityProvider, getIdentityProviders } from '../../../api/users';
import { getIdentityProviders } from '../../../api/users';
import { IdentityProvider } from '../../../app/types';
import { getBaseUrl } from '../../../helpers/urls';

interface Props {

+ 2
- 2
server/sonar-web/src/main/js/apps/sessions/components/Logout.tsx View File

@@ -42,9 +42,9 @@ class Logout extends React.PureComponent<Props> {

render() {
return (
<div>
<div className="page page-limited">
<GlobalMessagesContainer />
{translate('logging_out')}
<div className="text-center">{translate('logging_out')}</div>
</div>
);
}

+ 13
- 7
server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.css View File

@@ -18,12 +18,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
.oauth-providers > ul {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
width: 180px;
margin-left: auto;
margin-right: auto;
}

.oauth-providers > ul > li {
position: relative;
margin-bottom: 30px;
}

@@ -32,27 +33,32 @@
width: 180px;
line-height: 22px;
padding: 8px 12px;
border: none;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 2px;
box-sizing: border-box;
background-color: var(--darkBlue);
color: #fff;
white-space: nowrap;
overflow: hidden;
text-align: center;
text-overflow: ellipsis;
}

.oauth-providers > ul > li > a:hover,
.oauth-providers > ul > li > a:focus {
box-shadow: inset 0 0 16px rgba(0, 0, 0, 0.3);
box-shadow: 0 0 16px rgba(0, 0, 0, 0.2);
}

.oauth-providers > ul > li > a > span {
padding-left: 6px;
}

.oauth-providers + form {
.oauth-providers-help {
position: absolute;
top: 12px;
right: -32px;
}

.oauth-providers + .login-form {
padding-top: 30px;
border-top: 1px solid var(--barBorderColor);
}

+ 17
- 6
server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.tsx View File

@@ -19,18 +19,20 @@
*/
import * as React from 'react';
import { translateWithParameters } from '../../../helpers/l10n';
import { IdentityProvider } from '../../../api/users';
import * as theme from '../../../app/theme';
import { IdentityProvider } from '../../../app/types';
import Tooltip from '../../../components/controls/Tooltip';
import HelpIcon from '../../../components/icons-components/HelpIcon';
import { getTextColor } from '../../../helpers/colors';
import { getBaseUrl } from '../../../helpers/urls';
import './OAuthProviders.css';

interface Props {
formatLabel?: (name: string) => string;
identityProviders: IdentityProvider[];
returnTo: string;
}

export default function OAuthProviders(props: Props) {
const formatLabel = props.formatLabel || defaultFormatLabel;
return (
<section className="oauth-providers">
<ul>
@@ -41,16 +43,25 @@ export default function OAuthProviders(props: Props) {
`${getBaseUrl()}/sessions/init/${identityProvider.key}` +
`?return_to=${encodeURIComponent(props.returnTo)}`
}
style={{ backgroundColor: identityProvider.backgroundColor }}
title={formatLabel(identityProvider.name)}>
style={{
backgroundColor: identityProvider.backgroundColor,
color: getTextColor(identityProvider.backgroundColor, theme.secondFontColor)
}}>
<img
alt={identityProvider.name}
width="20"
height="20"
src={getBaseUrl() + identityProvider.iconPath}
/>
<span>{formatLabel(identityProvider.name)}</span>
<span>{defaultFormatLabel(identityProvider.name)}</span>
</a>
{identityProvider.helpMessage && (
<Tooltip overlay={identityProvider.helpMessage}>
<div className="oauth-providers-help">
<HelpIcon fill={theme.blue} />
</div>
</Tooltip>
)}
</li>
))}
</ul>

+ 13
- 9
server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.tsx View File

@@ -33,17 +33,21 @@ export default function Unauthorized(props: Props) {
const { message } = props.location.query;

return (
<div className="text-center">
<p id="unauthorized">{translate('unauthorized.message')}</p>
<div id="bd" className="page-wrapper-simple">
<div id="nonav" className="page-simple">
<div className="text-center">
<p id="unauthorized">{translate('unauthorized.message')}</p>

{!!message && (
<p className="spacer-top">
{translate('unauthorized.reason')} {message}
</p>
)}
{!!message && (
<p className="spacer-top">
{translate('unauthorized.reason')} {message}
</p>
)}

<div className="big-spacer-top">
<Link to="/">{translate('layout.home')}</Link>
<div className="big-spacer-top">
<Link to="/">{translate('layout.home')}</Link>
</div>
</div>
</div>
</div>
);

+ 97
- 87
server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/EmailAlreadyExists-test.tsx.snap View File

@@ -1,108 +1,118 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`render 1`] = `
<div>
<div
className="page-wrapper-simple"
id="bd"
>
<div
className="big-spacer-bottom js-existing-account"
className="page-simple"
id="nonav"
>
<p
className="little-spacer-bottom"
<div
className="big-spacer-bottom js-existing-account"
>
<FormattedMessage
defaultMessage="sessions.email_already_exists.1"
id="sessions.email_already_exists.1"
values={
<p
className="little-spacer-bottom"
>
<FormattedMessage
defaultMessage="sessions.email_already_exists.1"
id="sessions.email_already_exists.1"
values={
Object {
"email": <strong>
mail@example.com
</strong>,
}
}
/>
</p>
<div
className="identity-provider"
style={
Object {
"email": <strong>
mail@example.com
</strong>,
"backgroundColor": "#205081",
"color": "#fff",
}
}
/>
</p>
>
<img
alt="Bitbucket"
className="little-spacer-right"
height="14"
src="/static/authbitbucket/bitbucket.svg"
width="14"
/>
bar
</div>
</div>
<div
className="identity-provider"
style={
Object {
"backgroundColor": "#205081",
}
}
className="big-spacer-bottom js-new-account"
>
<img
alt="Bitbucket"
className="little-spacer-right"
height="14"
src="/static/authbitbucket/bitbucket.svg"
width="14"
/>
bar
<p
className="little-spacer-bottom"
>
sessions.email_already_exists.2
</p>
<div
className="identity-provider"
style={
Object {
"backgroundColor": "#444444",
"color": "#fff",
}
}
>
<img
alt="GitHub"
className="little-spacer-right"
height="14"
src="/static/authgithub/github.svg"
width="14"
/>
foo
</div>
</div>
</div>
<div
className="big-spacer-bottom js-new-account"
>
<p
className="little-spacer-bottom"
>
sessions.email_already_exists.2
</p>
<div
className="identity-provider"
style={
Object {
"backgroundColor": "#444444",
}
}
className="alert alert-warning"
>
<img
alt="GitHub"
className="little-spacer-right"
height="14"
src="/static/authgithub/github.svg"
width="14"
/>
foo
sessions.email_already_exists.3
<ul
className="list-styled"
>
<li
className="spacer-top"
>
sessions.email_already_exists.4
</li>
<li
className="spacer-top"
>
sessions.email_already_exists.5
</li>
<li
className="spacer-top"
>
sessions.email_already_exists.6
</li>
</ul>
</div>
</div>
<div
className="alert alert-warning"
>
sessions.email_already_exists.3
<ul
className="list-styled"
<div
className="big-spacer-top text-right"
>
<li
className="spacer-top"
<a
className="button js-continue"
href="/sessions/init/github?allowEmailShift=true"
>
sessions.email_already_exists.4
</li>
<li
className="spacer-top"
continue
</a>
<a
className="big-spacer-left js-cancel"
href="/"
>
sessions.email_already_exists.5
</li>
<li
className="spacer-top"
>
sessions.email_already_exists.6
</li>
</ul>
</div>
<div
className="big-spacer-top text-right"
>
<a
className="button js-continue"
href="/sessions/init/github?allowEmailShift=true"
>
continue
</a>
<a
className="big-spacer-left js-cancel"
href="/"
>
cancel
</a>
cancel
</a>
</div>
</div>
</div>
`;

+ 6
- 0
server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/LoginForm-test.tsx.snap View File

@@ -2,6 +2,7 @@

exports[`expands more options 1`] = `
<div
className="login-page"
id="login_form"
>
<h1
@@ -38,6 +39,7 @@ exports[`expands more options 1`] = `

exports[`expands more options 2`] = `
<div
className="login-page"
id="login_form"
>
<h1
@@ -59,6 +61,7 @@ exports[`expands more options 2`] = `
returnTo=""
/>
<form
className="login-form"
onSubmit={[Function]}
>
<Connect(GlobalMessages) />
@@ -130,6 +133,7 @@ exports[`expands more options 2`] = `

exports[`logs in with identity provider 1`] = `
<div
className="login-page"
id="login_form"
>
<h1
@@ -166,6 +170,7 @@ exports[`logs in with identity provider 1`] = `

exports[`logs in with simple credentials 1`] = `
<div
className="login-page"
id="login_form"
>
<h1
@@ -174,6 +179,7 @@ exports[`logs in with simple credentials 1`] = `
login.login_to_sonarqube
</h1>
<form
className="login-form"
onSubmit={[Function]}
>
<Connect(GlobalMessages) />

+ 2
- 2
server/sonar-web/src/main/js/apps/users/UsersApp.tsx View File

@@ -26,8 +26,8 @@ import Search from './Search';
import UsersList from './UsersList';
import { parseQuery, Query, serializeQuery } from './utils';
import ListFooter from '../../components/controls/ListFooter';
import { getIdentityProviders, IdentityProvider, searchUsers, User } from '../../api/users';
import { Paging } from '../../app/types';
import { getIdentityProviders, searchUsers } from '../../api/users';
import { Paging, IdentityProvider, User } from '../../app/types';
import { translate } from '../../helpers/l10n';

interface Props {

+ 1
- 1
server/sonar-web/src/main/js/apps/users/UsersList.tsx View File

@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import UserListItem from './components/UserListItem';
import { IdentityProvider, User } from '../../api/users';
import { IdentityProvider, User } from '../../app/types';
import { translate } from '../../helpers/l10n';

interface Props {

server/sonar-web/src/main/js/apps/users/__tests__/UsersList.tsx → server/sonar-web/src/main/js/apps/users/__tests__/UsersList-test.tsx View File


server/sonar-web/src/main/js/apps/users/__tests__/__snapshots__/UsersList.tsx.snap → server/sonar-web/src/main/js/apps/users/__tests__/__snapshots__/UsersList-test.tsx.snap View File


+ 2
- 1
server/sonar-web/src/main/js/apps/users/components/DeactivateForm.tsx View File

@@ -19,7 +19,8 @@
*/
import * as React from 'react';
import Modal from '../../../components/controls/Modal';
import { deactivateUser, User } from '../../../api/users';
import { deactivateUser } from '../../../api/users';
import { User } from '../../../app/types';
import { translate, translateWithParameters } from '../../../helpers/l10n';

export interface Props {

+ 1
- 1
server/sonar-web/src/main/js/apps/users/components/GroupsForm.tsx View File

@@ -19,9 +19,9 @@
*/
import * as React from 'react';
import * as escapeHtml from 'escape-html';
import { User } from '../../../app/types';
import Modal from '../../../components/controls/Modal';
import SelectList from '../../../components/SelectList';
import { User } from '../../../api/users';
import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/urls';


+ 2
- 1
server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx View File

@@ -21,8 +21,9 @@ import * as React from 'react';
import Modal from '../../../components/controls/Modal';
import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage';
import throwGlobalError from '../../../app/utils/throwGlobalError';
import { User } from '../../../app/types';
import { parseError } from '../../../helpers/request';
import { changePassword, User } from '../../../api/users';
import { changePassword } from '../../../api/users';
import { translate } from '../../../helpers/l10n';

interface Props {

+ 1
- 1
server/sonar-web/src/main/js/apps/users/components/TokensFormModal.tsx View File

@@ -38,8 +38,8 @@
*/
import * as React from 'react';
import TokensForm from './TokensForm';
import { User } from '../../../app/types';
import Modal from '../../../components/controls/Modal';
import { User } from '../../../api/users';
import { translate } from '../../../helpers/l10n';

interface Props {

+ 1
- 1
server/sonar-web/src/main/js/apps/users/components/UserActions.tsx View File

@@ -21,7 +21,7 @@ import * as React from 'react';
import DeactivateForm from './DeactivateForm';
import PasswordForm from './PasswordForm';
import UserForm from './UserForm';
import { User } from '../../../api/users';
import { User } from '../../../app/types';
import ActionsDropdown, {
ActionsDropdownItem,
ActionsDropdownDivider

+ 2
- 1
server/sonar-web/src/main/js/apps/users/components/UserForm.tsx View File

@@ -23,7 +23,8 @@ import UserScmAccountInput from './UserScmAccountInput';
import Modal from '../../../components/controls/Modal';
import throwGlobalError from '../../../app/utils/throwGlobalError';
import { parseError } from '../../../helpers/request';
import { createUser, updateUser, User } from '../../../api/users';
import { createUser, updateUser } from '../../../api/users';
import { User } from '../../../app/types';
import { translate, translateWithParameters } from '../../../helpers/l10n';

export interface Props {

+ 1
- 1
server/sonar-web/src/main/js/apps/users/components/UserGroups.tsx View File

@@ -20,7 +20,7 @@
import * as React from 'react';
import GroupsForm from './GroupsForm';
import BulletListIcon from '../../../components/icons-components/BulletListIcon';
import { User } from '../../../api/users';
import { User } from '../../../app/types';
import { ButtonIcon } from '../../../components/ui/buttons';
import { translate, translateWithParameters } from '../../../helpers/l10n';


+ 1
- 1
server/sonar-web/src/main/js/apps/users/components/UserListItem.tsx View File

@@ -23,7 +23,7 @@ import UserActions from './UserActions';
import UserGroups from './UserGroups';
import UserListItemIdentity from './UserListItemIdentity';
import UserScmAccounts from './UserScmAccounts';
import { IdentityProvider, User } from '../../../api/users';
import { IdentityProvider, User } from '../../../app/types';
import BulletListIcon from '../../../components/icons-components/BulletListIcon';
import Avatar from '../../../components/ui/Avatar';
import { ButtonIcon } from '../../../components/ui/buttons';

+ 8
- 2
server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx View File

@@ -18,7 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { IdentityProvider, User } from '../../../api/users';
import * as theme from '../../../app/theme';
import { IdentityProvider, User } from '../../../app/types';
import { getTextColor } from '../../../helpers/colors';
import { getBaseUrl } from '../../../helpers/urls';

interface Props {
@@ -57,9 +59,13 @@ export function ExternalProvider({ identityProvider, user }: Props) {
<div className="js-user-identity-provider little-spacer-top">
<div
className="identity-provider"
style={{ 'background-color': identityProvider.backgroundColor }}>
style={{
'background-color': identityProvider.backgroundColor,
color: getTextColor(identityProvider.backgroundColor, theme.secondFontColor)
}}>
<img
alt={identityProvider.name}
className="little-spacer-right"
src={getBaseUrl() + identityProvider.iconPath}
width="14"
height="14"

+ 1
- 23
server/sonar-web/src/main/js/components/ui/GenericAvatar.tsx View File

@@ -19,6 +19,7 @@
*/
import * as React from 'react';
import * as classNames from 'classnames';
import { stringToColor, getTextColor } from '../../helpers/colors';

interface Props {
className?: string;
@@ -56,26 +57,3 @@ export default function GenericAvatar({ className, name, size }: Props) {
</div>
);
}

/* eslint-disable no-bitwise, no-mixed-operators */
function stringToColor(str: string) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let color = '#';
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff;
color += ('00' + value.toString(16)).substr(-2);
}
return color;
}

function getTextColor(background: string) {
const rgb = parseInt(background.substr(1), 16);
const r = (rgb >> 16) & 0xff;
const g = (rgb >> 8) & 0xff;
const b = (rgb >> 0) & 0xff;
const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
return luma > 140 ? '#222' : '#fff';
}

+ 48
- 0
server/sonar-web/src/main/js/helpers/colors.ts View File

@@ -0,0 +1,48 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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.
*/

/* eslint-disable no-bitwise, no-mixed-operators */
export function stringToColor(str: string) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
let color = '#';
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff;
color += ('00' + value.toString(16)).substr(-2);
}
return color;
}

export function getTextColor(background: string, dark = '#222', light = '#fff') {
background = background.substr(1);
if (background.length === 3) {
// shortcut notation: #f90
background =
background[0] + background[0] + background[1] + background[1] + background[2] + background[2];
}
const rgb = parseInt(background.substr(1), 16);
const r = (rgb >> 16) & 0xff;
const g = (rgb >> 8) & 0xff;
const b = (rgb >> 0) & 0xff;
const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
return luma > 140 ? dark : light;
}

+ 3
- 3
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -528,11 +528,11 @@ process.fail=Failed

sessions.log_in=Log in
sessions.email_already_exists.1=The email address {email} is already associated to this user account:
sessions.email_already_exists.2=By clicking on "Continue" you will associate this email address to a new user account:
sessions.email_already_exists.2=By clicking on "Continue" you will associate this email address to another user account:
sessions.email_already_exists.3=This means the following:
sessions.email_already_exists.4=Your email address will be erased from this account.
sessions.email_already_exists.4=Your email address will be erased from the first account.
sessions.email_already_exists.5=You will no longer receive email notifications from this account.
sessions.email_already_exists.6=Issues won't be automatically assigned on the first account anymore.
sessions.email_already_exists.6=Issues won't be automatically assigned to this account anymore.


#------------------------------------------------------------------------------

Loading…
Cancel
Save