Browse Source

SONAR-21384 - Convert header from security user to MIUI

tags/10.4.0.87286
Kevin Silva 5 months ago
parent
commit
06428e2f26

+ 30
- 26
server/sonar-web/src/main/js/apps/users/Header.tsx View File

@@ -17,11 +17,10 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { ButtonPrimary, FlagMessage, Link, Title } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import DocLink from '../../components/common/DocLink';
import { Button } from '../../components/controls/buttons';
import { Alert } from '../../components/ui/Alert';
import { useDocUrl } from '../../helpers/docs';
import { translate } from '../../helpers/l10n';
import UserForm from './components/UserForm';

@@ -30,41 +29,46 @@ interface Props {
}

export default function Header(props: Props) {
const getDocUrl = useDocUrl();
const [openUserForm, setOpenUserForm] = React.useState(false);

const { manageProvider } = props;
return (
<div className="page-header null-spacer-bottom">
<h2 className="page-title">{translate('users.page')}</h2>
<div>
<div className="sw-flex sw-justify-between">
<Title>{translate('users.page')}</Title>

<div className="page-actions">
<Button
<ButtonPrimary
id="users-create"
disabled={manageProvider !== undefined}
onClick={() => setOpenUserForm(true)}
>
{translate('users.create_user')}
</Button>
</ButtonPrimary>
</div>
<div>
{manageProvider === undefined ? (
<span>{translate('users.page.description')}</span>
) : (
<FlagMessage className="sw-max-w-3/4" variant="info">
<span>
<FormattedMessage
defaultMessage={translate('users.page.managed_description')}
id="users.page.managed_description"
values={{
provider: manageProvider,
link: (
<Link to={getDocUrl('/instance-administration/authentication/overview/')}>
{translate('documentation')}
</Link>
),
}}
/>
</span>
</FlagMessage>
)}
</div>

{manageProvider === undefined ? (
<p className="page-description">{translate('users.page.description')}</p>
) : (
<Alert className="page-description max-width-100" variant="info">
<FormattedMessage
defaultMessage={translate('users.page.managed_description')}
id="users.page.managed_description"
values={{
provider: manageProvider,
link: (
<DocLink to="/instance-administration/authentication/overview/">
{translate('documentation')}
</DocLink>
),
}}
/>
</Alert>
)}
{openUserForm && (
<UserForm onClose={() => setOpenUserForm(false)} isInstanceManaged={false} />
)}

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

@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { subDays, subSeconds } from 'date-fns';
import { HelperHintIcon, InputSearch, InputSelect, StyledPageTitle } from 'design-system';
import React, { useEffect, useMemo, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import { getIdentityProviders } from '../../api/users';
@@ -26,8 +27,7 @@ import GitLabSynchronisationWarning from '../../app/components/GitLabSynchronisa
import HelpTooltip from '../../components/controls/HelpTooltip';
import ListFooter from '../../components/controls/ListFooter';
import { ManagedFilter } from '../../components/controls/ManagedFilter';
import SearchBox from '../../components/controls/SearchBox';
import Select, { LabelValueSelectOption } from '../../components/controls/Select';
import { LabelValueSelectOption } from '../../components/controls/Select';
import Suggestions from '../../components/embed-docs-modal/Suggestions';
import Spinner from '../../components/ui/Spinner';
import { now, toISO8601WithOffsetString } from '../../helpers/dates';
@@ -102,17 +102,21 @@ export default function UsersApp() {
managed={managed}
setManaged={(m) => setManaged(m)}
/>
<SearchBox
<InputSearch
id="users-search"
minLength={2}
onChange={(search: string) => setSearch(search)}
placeholder={translate('search.search_by_login_or_name')}
value={search}
/>
<div className="sw-ml-4">
<Select
<div className="sw-flex sw-items-center sw-ml-4">
<StyledPageTitle as="label" className="sw-body-sm-highlight sw-mr-2">
{translate('users.filter.by')}
</StyledPageTitle>
<InputSelect
className="sw-body-sm"
size="medium"
id="users-activity-filter"
className="input-large"
isDisabled={isLoading}
onChange={(userActivity: LabelValueSelectOption<UserActivity>) =>
setUsersActivity(userActivity.value)
@@ -131,7 +135,9 @@ export default function UsersApp() {
<p>{translate('users.activity_filter.helptext.sonarlint')}</p>
</>
}
/>
>
<HelperHintIcon />
</HelpTooltip>
</div>
</div>
<Spinner loading={isLoading}>

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

@@ -18,11 +18,16 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { AxiosError, AxiosResponse } from 'axios';
import {
ButtonPrimary,
ButtonSecondary,
FlagMessage,
FormField,
InputField,
Modal,
Spinner,
} from 'design-system';
import * as React from 'react';
import SimpleModal from '../../../components/controls/SimpleModal';
import { Button, ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
import { Alert } from '../../../components/ui/Alert';
import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker';
import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
import { addGlobalErrorMessage } from '../../../helpers/globalMessages';
import { translate, translateWithParameters } from '../../../helpers/l10n';
@@ -43,8 +48,8 @@ const INTERNAL_SERVER_ERROR = 500;
export default function UserForm(props: Props) {
const { user, isInstanceManaged } = props;

const { mutate: createUser } = usePostUserMutation();
const { mutate: updateUser } = useUpdateUserMutation();
const { mutate: createUser, isLoading: isLoadingCreate } = usePostUserMutation();
const { mutate: updateUser, isLoading: isLoadingUserUpdate } = useUpdateUserMutation();

const [email, setEmail] = React.useState<string>(user?.email ?? '');
const [login, setLogin] = React.useState<string>(user?.login ?? '');
@@ -64,7 +69,18 @@ export default function UserForm(props: Props) {
}
};

const handleCreateUser = () => {
React.useEffect(() => {
document.getElementById('it__error-message')?.scrollIntoView({
block: 'start',
});
}, [error]);

const handleClose = () => {
props.onClose();
};

const handleCreateUser = (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault();
createUser(
{
email: email || undefined,
@@ -77,9 +93,9 @@ export default function UserForm(props: Props) {
);
};

const handleUpdateUser = () => {
const handleUpdateUser = (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault();
const { user } = props;

updateUser(
{
id: user?.id!,
@@ -115,127 +131,130 @@ export default function UserForm(props: Props) {
const header = user ? translate('users.update_user') : translate('users.create_user');

return (
<SimpleModal
header={header}
onClose={props.onClose}
onSubmit={user ? handleUpdateUser : handleCreateUser}
size="small"
>
{({ onCloseClick, onFormSubmit, submitting }) => (
<form autoComplete="off" id="user-form" onSubmit={onFormSubmit}>
<header className="modal-head">
<h2>{header}</h2>
</header>

<div className="modal-body modal-container">
{error && <Alert variant="error">{error}</Alert>}

{!error && user && !user.local && (
<Alert variant="warning">{translate('users.cannot_update_delegated_user')}</Alert>
)}

<MandatoryFieldsExplanation className="modal-field" />

{!user && (
<div className="modal-field">
<label htmlFor="create-user-login">
{translate('login')}
<MandatoryFieldMarker />
</label>
<input
autoComplete="off"
autoFocus
id="create-user-login"
maxLength={255}
minLength={3}
name="login"
onChange={(e) => setLogin(e.currentTarget.value)}
required={!isInstanceManaged}
type="text"
value={login}
/>
<p className="note">{translateWithParameters('users.minimum_x_characters', 3)}</p>
</div>
)}
<div className="modal-field">
<label htmlFor="create-user-name">
{translate('name')}
{!isInstanceManaged && <MandatoryFieldMarker />}
</label>
<input
<Modal
headerTitle={header}
onClose={handleClose}
body={
<form
autoComplete="off"
id="user-form"
onSubmit={user ? handleUpdateUser : handleCreateUser}
>
{error && (
<FlagMessage id="it__error-message" className="sw-mb-4" variant="error">
{error}
</FlagMessage>
)}

{!error && user && !user.local && (
<FlagMessage variant="warning">
{translate('users.cannot_update_delegated_user')}
</FlagMessage>
)}
<div className="sw-mb-4">
<MandatoryFieldsExplanation />
</div>

{!user && (
<FormField
description={translateWithParameters('users.minimum_x_characters', 3)}
label={translate('login')}
htmlFor="create-user-login"
required={!isInstanceManaged}
>
<InputField
autoComplete="off"
autoFocus={!!user}
disabled={(user && !user.local) || isInstanceManaged}
id="create-user-name"
maxLength={200}
name="name"
onChange={(e) => setName(e.currentTarget.value)}
required={!isInstanceManaged}
maxLength={255}
minLength={3}
size="full"
id="create-user-login"
name="login"
onChange={(e) => setLogin(e.currentTarget.value)}
type="text"
value={name}
value={login}
/>
</div>
<div className="modal-field">
<label htmlFor="create-user-email">{translate('users.email')}</label>
<input
</FormField>
)}

<FormField
label={translate('name')}
htmlFor="create-user-name"
required={!isInstanceManaged}
>
<InputField
autoComplete="off"
disabled={(user && !user.local) || isInstanceManaged}
size="full"
maxLength={200}
id="create-user-name"
name="name"
onChange={(e) => setName(e.currentTarget.value)}
type="text"
value={name}
/>
</FormField>

<FormField label={translate('users.email')} htmlFor="create-user-email">
<InputField
autoComplete="off"
disabled={(user && !user.local) || isInstanceManaged}
size="full"
maxLength={100}
id="create-user-email"
name="email"
onChange={(e) => setEmail(e.currentTarget.value)}
type="email"
value={email}
/>
</FormField>

{!user && (
<FormField required label={translate('password')} htmlFor="create-user-password">
<InputField
autoComplete="off"
disabled={(user && !user.local) || isInstanceManaged}
id="create-user-email"
maxLength={100}
name="email"
onChange={(e) => setEmail(e.currentTarget.value)}
type="email"
value={email}
size="full"
id="create-user-password"
name="password"
onChange={(e) => setPassword(e.currentTarget.value)}
type="password"
value={password}
/>
</FormField>
)}
<FormField
description={translate('user.login_or_email_used_as_scm_account')}
label={translate('my_profile.scm_accounts')}
>
{scmAccounts.map((scm, idx) => (
<UserScmAccountInput
idx={idx}
key={idx}
onChange={handleUpdateScmAccount}
onRemove={handleRemoveScmAccount}
scmAccount={scm}
/>
))}
<div>
<ButtonSecondary className="it__scm-account-add" onClick={handleAddScmAccount}>
{translate('add_verb')}
</ButtonSecondary>
</div>
{!user && (
<div className="modal-field">
<label htmlFor="create-user-password">
{translate('password')}
<MandatoryFieldMarker />
</label>
<input
autoComplete="off"
id="create-user-password"
name="password"
onChange={(e) => setPassword(e.currentTarget.value)}
required
type="password"
value={password}
/>
</div>
)}
<div className="modal-field">
<fieldset>
<legend>{translate('my_profile.scm_accounts')}</legend>
{scmAccounts.map((scm, idx) => (
<UserScmAccountInput
idx={idx}
key={idx}
onChange={handleUpdateScmAccount}
onRemove={handleRemoveScmAccount}
scmAccount={scm}
/>
))}
<div className="spacer-bottom">
<Button className="js-scm-account-add" onClick={handleAddScmAccount}>
{translate('add_verb')}
</Button>
</div>
</fieldset>
<p className="note">{translate('user.login_or_email_used_as_scm_account')}</p>
</div>
</div>

<footer className="modal-foot">
{submitting && <i className="spinner spacer-right" />}
<SubmitButton disabled={submitting}>
{user ? translate('update_verb') : translate('create')}
</SubmitButton>
<ResetButtonLink onClick={onCloseClick}>{translate('cancel')}</ResetButtonLink>
</footer>
</FormField>
</form>
)}
</SimpleModal>
}
primaryButton={
<>
<Spinner loading={isLoadingCreate || isLoadingUserUpdate} />
<ButtonPrimary
disabled={isLoadingCreate || isLoadingUserUpdate}
type="submit"
form="user-form"
>
{user ? translate('update_verb') : translate('create')}
</ButtonPrimary>
</>
}
secondaryButtonLabel={translate('cancel')}
/>
);
}

+ 9
- 7
server/sonar-web/src/main/js/apps/users/components/UserScmAccountInput.tsx View File

@@ -17,8 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { DestructiveIcon, InputField, TrashIcon } from 'design-system';
import * as React from 'react';
import { DeleteButton } from '../../../components/controls/buttons';
import { translate, translateWithParameters } from '../../../helpers/l10n';

export interface Props {
@@ -36,8 +36,10 @@ export default function UserScmAccountInput(props: Props) {
: translate('users.create_user.scm_account_new');

return (
<div className="js-scm-account display-flex-row spacer-bottom">
<input
<div className="it__scm-account sw-flex sw-mb-2">
<InputField
className="sw-mr-1"
size="full"
maxLength={255}
onChange={(event) => {
props.onChange(idx, event.currentTarget.value);
@@ -46,11 +48,11 @@ export default function UserScmAccountInput(props: Props) {
aria-label={inputAriaLabel}
value={scmAccount}
/>
<DeleteButton
<DestructiveIcon
Icon={TrashIcon}
aria-label={translateWithParameters('remove_x', inputAriaLabel)}
onClick={() => {
props.onRemove(idx);
}}
onClick={() => props.onRemove(idx)}
stopPropagation={false}
/>
</div>
);

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

@@ -5054,6 +5054,7 @@ users.change_admin_password.form.confirm=Confirm password for user 'admin'
users.change_admin_password.form.cannot_use_default_password=You must choose a password that is different from the default password.
users.change_admin_password.form.success=The admin user's password was successfully changed.
users.change_admin_password.form.continue_to_app=Continue to SonarQube
users.filter.by=Filter by

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

Loading…
Cancel
Save