@@ -19,6 +19,13 @@ | |||
*/ | |||
import { getJSON, post, RequestData } from '../helpers/request'; | |||
export interface IdentityProvider { | |||
backgroundColor: string; | |||
iconPath: string; | |||
key: string; | |||
name: string; | |||
} | |||
export function getCurrentUser(): Promise<any> { | |||
return getJSON('/api/users/current'); | |||
} | |||
@@ -43,7 +50,7 @@ export function getUserGroups(login: string, organization?: string): Promise<any | |||
return getJSON('/api/users/groups', data); | |||
} | |||
export function getIdentityProviders(): Promise<any> { | |||
export function getIdentityProviders(): Promise<{ identityProviders: IdentityProvider[] }> { | |||
return getJSON('/api/users/identity_providers'); | |||
} | |||
@@ -17,21 +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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import GlobalFooterContainer from './GlobalFooterContainer'; | |||
import NavBar from '../../components/nav/NavBar'; | |||
/*:: | |||
type Props = { | |||
children?: React.Element<*> | Array<React.Element<*>>, | |||
hideLoggedInInfo?: boolean | |||
}; | |||
*/ | |||
export default class SimpleContainer extends React.PureComponent { | |||
/*:: props: Props; */ | |||
interface Props { | |||
children?: React.ReactNode; | |||
hideLoggedInInfo?: boolean; | |||
} | |||
export default class SimpleContainer extends React.PureComponent<Props> { | |||
componentDidMount() { | |||
const html = document.querySelector('html'); | |||
if (html) { | |||
@@ -50,7 +45,7 @@ export default class SimpleContainer extends React.PureComponent { | |||
return ( | |||
<div className="global-container"> | |||
<div className="page-wrapper" id="container"> | |||
<NavBar className="navbar-global" id="global-navigation" height={30} /> | |||
<NavBar className="navbar-global" height={30} /> | |||
<div id="bd" className="page-wrapper-simple"> | |||
<div id="nonav" className="page-simple"> |
@@ -17,37 +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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import OAuthProviders from './OAuthProviders'; | |||
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { IdentityProvider } from '../../../api/users'; | |||
/*:: | |||
type Props = { | |||
identityProviders: Array<{ | |||
backgroundColor: string, | |||
iconPath: string, | |||
key: string, | |||
name: string | |||
}>, | |||
onSubmit: (string, string) => void | |||
}; | |||
*/ | |||
/*:: | |||
type State = { | |||
collapsed: boolean, | |||
login: string, | |||
password: string | |||
}; | |||
*/ | |||
interface Props { | |||
identityProviders: IdentityProvider[]; | |||
onSubmit: (login: string, password: string) => void; | |||
} | |||
export default class LoginForm extends React.PureComponent { | |||
/*:: props: Props; */ | |||
/*:: state: State; */ | |||
interface State { | |||
collapsed: boolean; | |||
login: string; | |||
password: string; | |||
} | |||
constructor(props /*: Props */) { | |||
export default class LoginForm extends React.PureComponent<Props, State> { | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { | |||
collapsed: props.identityProviders.length > 0, | |||
@@ -56,16 +45,22 @@ export default class LoginForm extends React.PureComponent { | |||
}; | |||
} | |||
handleSubmit = (event /*: Event */) => { | |||
handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { | |||
event.preventDefault(); | |||
this.props.onSubmit(this.state.login, this.state.password); | |||
}; | |||
handleMoreOptionsClick = (event /*: Event */) => { | |||
handleMoreOptionsClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
this.setState({ collapsed: false }); | |||
}; | |||
handleLoginChange = (event: React.SyntheticEvent<HTMLInputElement>) => | |||
this.setState({ login: event.currentTarget.value }); | |||
handlePwdChange = (event: React.SyntheticEvent<HTMLInputElement>) => | |||
this.setState({ password: event.currentTarget.value }); | |||
render() { | |||
return ( | |||
<div id="login_form"> | |||
@@ -97,12 +92,12 @@ export default class LoginForm extends React.PureComponent { | |||
id="login" | |||
name="login" | |||
className="login-input" | |||
maxLength="255" | |||
maxLength={255} | |||
required={true} | |||
autoFocus={true} | |||
placeholder={translate('login')} | |||
value={this.state.login} | |||
onChange={e => this.setState({ login: e.target.value })} | |||
onChange={this.handleLoginChange} | |||
/> | |||
</div> | |||
@@ -118,7 +113,7 @@ export default class LoginForm extends React.PureComponent { | |||
required={true} | |||
placeholder={translate('password')} | |||
value={this.state.password} | |||
onChange={e => this.setState({ password: e.target.value })} | |||
onChange={this.handlePwdChange} | |||
/> | |||
</div> | |||
@@ -127,9 +122,9 @@ export default class LoginForm extends React.PureComponent { | |||
<button name="commit" type="submit"> | |||
{translate('sessions.log_in')} | |||
</button> | |||
<a className="spacer-left" href={window.baseUrl + '/'}> | |||
<Link className="spacer-left" to="/"> | |||
{translate('cancel')} | |||
</a> | |||
</Link> | |||
</div> | |||
</div> | |||
</form> |
@@ -17,23 +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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import LoginForm from './LoginForm'; | |||
import { doLogin } from '../../../store/rootActions'; | |||
import { getAppState } from '../../../store/rootReducer'; | |||
import { getIdentityProviders } from '../../../api/users'; | |||
import { IdentityProvider, getIdentityProviders } from '../../../api/users'; | |||
import { getBaseUrl } from '../../../helpers/urls'; | |||
class LoginFormContainer extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
interface Props { | |||
doLogin: (login: string, password: string) => Promise<void>; | |||
location: { hash?: string; pathName: string; query: { return_to?: string } }; | |||
} | |||
static propTypes = { | |||
location: PropTypes.object.isRequired | |||
}; | |||
interface State { | |||
identityProviders?: IdentityProvider[]; | |||
} | |||
state = {}; | |||
class LoginFormContainer extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
state: State = {}; | |||
componentDidMount() { | |||
this.mounted = true; | |||
@@ -51,14 +54,12 @@ class LoginFormContainer extends React.PureComponent { | |||
handleSuccessfulLogin = () => { | |||
const { location } = this.props; | |||
const queryReturnTo = location.query['return_to']; | |||
const returnTo = queryReturnTo ? `${queryReturnTo}${location.hash}` : `${window.baseUrl}/`; | |||
window.location = returnTo; | |||
const returnTo = queryReturnTo ? `${queryReturnTo}${location.hash}` : `${getBaseUrl()}/`; | |||
window.location.href = returnTo; | |||
}; | |||
handleSubmit = (login /*: string */, password /*: string */) => { | |||
this.props.doLogin(login, password).then(this.handleSuccessfulLogin, () => { | |||
/* do nothing */ | |||
}); | |||
handleSubmit = (login: string, password: string) => { | |||
this.props.doLogin(login, password).then(this.handleSuccessfulLogin, () => {}); | |||
}; | |||
render() { | |||
@@ -72,10 +73,10 @@ class LoginFormContainer extends React.PureComponent { | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
const mapStateToProps = (state: any) => ({ | |||
appState: getAppState(state) | |||
}); | |||
const mapDispatchToProps = { doLogin }; | |||
export default connect(mapStateToProps, mapDispatchToProps)(LoginFormContainer); | |||
export default connect(mapStateToProps, mapDispatchToProps)(LoginFormContainer as any); |
@@ -17,25 +17,27 @@ | |||
* 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 * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer'; | |||
import RecentHistory from '../../../app/components/RecentHistory'; | |||
import { doLogout } from '../../../store/rootActions'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import RecentHistory from '../../../app/components/RecentHistory'; | |||
import { getBaseUrl } from '../../../helpers/urls'; | |||
class Logout extends React.PureComponent { | |||
interface Props { | |||
doLogout: () => Promise<void>; | |||
} | |||
class Logout extends React.PureComponent<Props> { | |||
componentDidMount() { | |||
this.props | |||
.doLogout() | |||
.then(() => { | |||
this.props.doLogout().then( | |||
() => { | |||
RecentHistory.clear(); | |||
window.location = window.baseUrl + '/'; | |||
}) | |||
.catch(() => { | |||
/* do nothing */ | |||
}); | |||
window.location.href = getBaseUrl() + '/'; | |||
}, | |||
() => {} | |||
); | |||
} | |||
render() { | |||
@@ -52,4 +54,4 @@ const mapStateToProps = () => ({}); | |||
const mapDispatchToProps = { doLogout }; | |||
export default connect(mapStateToProps, mapDispatchToProps)(Logout); | |||
export default connect(mapStateToProps, mapDispatchToProps)(Logout as any); |
@@ -17,43 +17,34 @@ | |||
* 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 * as React from 'react'; | |||
import { translateWithParameters } from '../../../helpers/l10n'; | |||
import { IdentityProvider } from '../../../api/users'; | |||
import { getBaseUrl } from '../../../helpers/urls'; | |||
/*:: | |||
type Props = { | |||
formatLabel?: string => string, | |||
identityProviders: Array<{ | |||
backgroundColor: string, | |||
iconPath: string, | |||
key: string, | |||
name: string | |||
}> | |||
}; | |||
*/ | |||
interface Props { | |||
formatLabel?: (name: string) => string; | |||
identityProviders: IdentityProvider[]; | |||
} | |||
export default function OAuthProviders(props /*: Props */) { | |||
export default function OAuthProviders(props: Props) { | |||
const formatLabel = props.formatLabel || defaultFormatLabel; | |||
return ( | |||
<section className="oauth-providers"> | |||
<ul> | |||
{props.identityProviders.map(identityProvider => ( | |||
<li key={identityProvider.key}> | |||
<a | |||
href={`${window.baseUrl}/sessions/init/${identityProvider.key}`} | |||
href={`${getBaseUrl()}/sessions/init/${identityProvider.key}`} | |||
style={{ backgroundColor: identityProvider.backgroundColor }} | |||
// $FlowFixMe formatLabel is always defined through defaultProps | |||
title={props.formatLabel(identityProvider.name)}> | |||
title={formatLabel(identityProvider.name)}> | |||
<img | |||
alt={identityProvider.name} | |||
width="20" | |||
height="20" | |||
src={window.baseUrl + identityProvider.iconPath} | |||
src={getBaseUrl() + identityProvider.iconPath} | |||
/> | |||
<span> | |||
{/* $FlowFixMe formatLabel is always defined through defaultProps */} | |||
{props.formatLabel(identityProvider.name)} | |||
</span> | |||
<span>{formatLabel(identityProvider.name)}</span> | |||
</a> | |||
</li> | |||
))} | |||
@@ -62,6 +53,6 @@ export default function OAuthProviders(props /*: Props */) { | |||
); | |||
} | |||
OAuthProviders.defaultProps = { | |||
formatLabel: (name /*: string */) => translateWithParameters('login.login_with_x', name) | |||
}; | |||
function defaultFormatLabel(name: string) { | |||
return translateWithParameters('login.login_with_x', name); | |||
} |
@@ -17,16 +17,13 @@ | |||
* 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 * as React from 'react'; | |||
import SimpleContainer from '../../../app/components/SimpleContainer'; | |||
/*:: | |||
type Props = { | |||
children?: React.Element<*> | Array<React.Element<*>> | |||
}; | |||
*/ | |||
interface Props { | |||
children?: React.ReactElement<any>; | |||
} | |||
export default function SimpleSessionsContainer({ children } /*: Props */) { | |||
export default function SimpleSessionsContainer({ children }: Props) { | |||
return <SimpleContainer hideLoggedInInfo={true}>{children}</SimpleContainer>; | |||
} |
@@ -19,6 +19,7 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
location: { | |||
@@ -33,14 +34,16 @@ export default function Unauthorized(props: Props) { | |||
return ( | |||
<div className="text-center"> | |||
<p id="unauthorized"> | |||
{"You're not authorized to access this page. Please contact the administrator."} | |||
</p> | |||
<p id="unauthorized">{translate('unauthorized.message')}</p> | |||
{!!message && <p className="spacer-top">Reason : {message}</p>} | |||
{!!message && ( | |||
<p className="spacer-top"> | |||
{translate('unauthorized.reason')} {message} | |||
</p> | |||
)} | |||
<div className="big-spacer-top"> | |||
<Link to="/">Home</Link> | |||
<Link to="/">{translate('layout.home')}</Link> | |||
</div> | |||
</div> | |||
); |
@@ -17,8 +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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import LoginForm from '../LoginForm'; | |||
import { change, click, submit } from '../../../../helpers/testUtils'; |
@@ -10,7 +10,6 @@ exports[`expands more options 1`] = ` | |||
login.login_to_sonarqube | |||
</h1> | |||
<OAuthProviders | |||
formatLabel={[Function]} | |||
identityProviders={ | |||
Array [ | |||
Object { | |||
@@ -46,7 +45,6 @@ exports[`expands more options 2`] = ` | |||
login.login_to_sonarqube | |||
</h1> | |||
<OAuthProviders | |||
formatLabel={[Function]} | |||
identityProviders={ | |||
Array [ | |||
Object { | |||
@@ -75,7 +73,7 @@ exports[`expands more options 2`] = ` | |||
autoFocus={true} | |||
className="login-input" | |||
id="login" | |||
maxLength="255" | |||
maxLength={255} | |||
name="login" | |||
onChange={[Function]} | |||
placeholder="login" | |||
@@ -114,12 +112,14 @@ exports[`expands more options 2`] = ` | |||
> | |||
sessions.log_in | |||
</button> | |||
<a | |||
<Link | |||
className="spacer-left" | |||
href="/" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/" | |||
> | |||
cancel | |||
</a> | |||
</Link> | |||
</div> | |||
</div> | |||
</form> | |||
@@ -136,7 +136,6 @@ exports[`logs in with identity provider 1`] = ` | |||
login.login_to_sonarqube | |||
</h1> | |||
<OAuthProviders | |||
formatLabel={[Function]} | |||
identityProviders={ | |||
Array [ | |||
Object { | |||
@@ -188,7 +187,7 @@ exports[`logs in with simple credentials 1`] = ` | |||
autoFocus={true} | |||
className="login-input" | |||
id="login" | |||
maxLength="255" | |||
maxLength={255} | |||
name="login" | |||
onChange={[Function]} | |||
placeholder="login" | |||
@@ -227,12 +226,14 @@ exports[`logs in with simple credentials 1`] = ` | |||
> | |||
sessions.log_in | |||
</button> | |||
<a | |||
<Link | |||
className="spacer-left" | |||
href="/" | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/" | |||
> | |||
cancel | |||
</a> | |||
</Link> | |||
</div> | |||
</div> | |||
</form> |
@@ -1350,6 +1350,9 @@ login.login_to_sonarqube=Log In to SonarQube | |||
login.more_options=More options | |||
login.login_with_x=Log in with {0} | |||
unauthorized.message=You're not authorized to access this page. Please contact the administrator. | |||
unauthorized.reason=Reason: | |||
#------------------------------------------------------------------------------ | |||
# | |||
# USERS & GROUPS PAGE |
@@ -36,7 +36,7 @@ | |||
<tr> | |||
<td>assertText</td> | |||
<td>bd</td> | |||
<td>*Reason : A functional error has happened*</td> | |||
<td>*Reason: A functional error has happened*</td> | |||
</tr> | |||
</tbody> | |||
</table> |
@@ -31,7 +31,7 @@ | |||
<tr> | |||
<td>waitForText</td> | |||
<td>bd</td> | |||
<td>*You're not authorized to access this page. Please contact the administrator.*Reason : 'fake-base-id-provider' users are not allowed to sign up*</td> | |||
<td>*You're not authorized to access this page. Please contact the administrator.*Reason: 'fake-base-id-provider' users are not allowed to sign up*</td> | |||
</tr> | |||
</tbody> | |||
</table> |
@@ -36,7 +36,7 @@ | |||
<tr> | |||
<td>assertText</td> | |||
<td>bd</td> | |||
<td>*Reason : A functional error has happened*</td> | |||
<td>*Reason: A functional error has happened*</td> | |||
</tr> | |||
</tbody> | |||
</table> |
@@ -31,7 +31,7 @@ | |||
<tr> | |||
<td>waitForText</td> | |||
<td>bd</td> | |||
<td>*You're not authorized to access this page. Please contact the administrator.*Reason : 'fake-oauth2-id-provider' users are not allowed to sign up*</td> | |||
<td>*You're not authorized to access this page. Please contact the administrator.*Reason: 'fake-oauth2-id-provider' users are not allowed to sign up*</td> | |||
</tr> | |||
</tbody> | |||
</table> |