* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import styled from '@emotion/styled';
import { formatDistance } from 'date-fns';
+import { CheckIcon, FlagMessage, FlagWarningIcon, Link, themeColor } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import Link from '../../components/common/Link';
-import CheckIcon from '../../components/icons/CheckIcon';
-import WarningIcon from '../../components/icons/WarningIcon';
import { Alert } from '../../components/ui/Alert';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { AlmSyncStatus } from '../../types/provisioning';
if (short) {
return status === TaskStatuses.Success ? (
<div>
- <span className="authentication-enabled spacer-left">
+ <IconWrapper className="sw-ml-2">
{warningMessage ? (
- <WarningIcon className="spacer-right" />
+ <FlagWarningIcon className="sw-mr-2" />
) : (
- <CheckIcon className="spacer-right" />
+ <CheckIcon width={32} height={32} className="sw-mr-2" />
)}
- </span>
+ </IconWrapper>
<i>
{warningMessage ? (
<FormattedMessage
values={{
date: formattedDate,
details: (
- <Link to="/admin/settings?category=authentication&tab=github">
+ <Link className="sw-ml-2" to="/admin/settings?category=authentication&tab=github">
{translate('settings.authentication.github.synchronization_details_link')}
</Link>
),
</i>
</div>
) : (
- <Alert variant="error">
+ <FlagMessage variant="error">
<FormattedMessage
id="settings.authentication.github.synchronization_failed_short"
defaultMessage={translate('settings.authentication.github.synchronization_failed_short')}
values={{
details: (
- <Link to="/admin/settings?category=authentication&tab=github">
+ <Link className="sw-ml-2" to="/admin/settings?category=authentication&tab=github">
{translate('settings.authentication.github.synchronization_details_link')}
</Link>
),
}}
/>
- </Alert>
+ </FlagMessage>
);
}
</>
);
}
+
+const IconWrapper = styled.span`
+ color: ${themeColor('iconSuccess')};
+`;
'/admin/permission_templates',
'/project/background_tasks',
'/admin/background_tasks',
+ '/admin/groups',
];
export default function GlobalContainer() {
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { InputSearch, LargeCenteredLayout, PageContentFontWrapper } from 'design-system';
import * as React from 'react';
import { useState } from 'react';
import { Helmet } from 'react-helmet-async';
import GitLabSynchronisationWarning from '../../app/components/GitLabSynchronisationWarning';
import ListFooter from '../../components/controls/ListFooter';
import { ManagedFilter } from '../../components/controls/ManagedFilter';
-import SearchBox from '../../components/controls/SearchBox';
import Suggestions from '../../components/embed-docs-modal/Suggestions';
import { translate } from '../../helpers/l10n';
import { useGroupsQueries } from '../../queries/groups';
import { Provider } from '../../types/types';
import Header from './components/Header';
import List from './components/List';
-import './groups.css';
export default function GroupsApp() {
const [search, setSearch] = useState<string>('');
const groups = data?.pages.flatMap((page) => page.groups) ?? [];
return (
- <>
- <Suggestions suggestions="user_groups" />
- <Helmet defer={false} title={translate('user_groups.page')} />
- <main className="page page-limited" id="groups-page">
- <Header manageProvider={manageProvider?.provider} />
- {manageProvider?.provider === Provider.Github && <GitHubSynchronisationWarning short />}
- {manageProvider?.provider === Provider.Gitlab && <GitLabSynchronisationWarning short />}
+ <LargeCenteredLayout>
+ <PageContentFontWrapper className="sw-my-8 sw-body-sm">
+ <Suggestions suggestions="user_groups" />
+ <Helmet defer={false} title={translate('user_groups.page')} />
+ <main>
+ <Header manageProvider={manageProvider?.provider} />
+ {manageProvider?.provider === Provider.Github && <GitHubSynchronisationWarning short />}
+ {manageProvider?.provider === Provider.Gitlab && <GitLabSynchronisationWarning short />}
- <div className="display-flex-justify-start big-spacer-bottom big-spacer-top">
- <ManagedFilter
- manageProvider={manageProvider?.provider}
- loading={isLoading}
- managed={managed}
- setManaged={setManaged}
- />
- <SearchBox
- id="groups-search"
- minLength={2}
- onChange={(q) => setSearch(q)}
- placeholder={translate('search.search_by_name')}
- value={search}
- />
- </div>
+ <div className="sw-flex sw-my-4">
+ <ManagedFilter
+ manageProvider={manageProvider?.provider}
+ loading={isLoading}
+ managed={managed}
+ setManaged={setManaged}
+ miui
+ />
+ <InputSearch
+ minLength={2}
+ size="large"
+ onChange={(q) => setSearch(q)}
+ placeholder={translate('search.search_by_name')}
+ value={search}
+ />
+ </div>
- <List groups={groups} manageProvider={manageProvider?.provider} />
+ <List groups={groups} manageProvider={manageProvider?.provider} />
- <div id="groups-list-footer">
<ListFooter
count={groups.length}
loading={isLoading}
loadMore={fetchNextPage}
ready={!isLoading}
total={data?.pages[0].page.total}
+ useMIUIButtons
/>
- </div>
- </main>
- </>
+ </main>
+ </PageContentFontWrapper>
+ </LargeCenteredLayout>
);
}
allFilter: byRole('radio', { name: 'all' }),
selectedFilter: byRole('radio', { name: 'selected' }),
unselectedFilter: byRole('radio', { name: 'unselected' }),
- localAndManagedFilter: byRole('button', { name: 'all' }),
- managedFilter: byRole('button', { name: 'managed' }),
- localFilter: byRole('button', { name: 'local' }),
+ localAndManagedFilter: byRole('radio', { name: 'all' }),
+ managedFilter: byRole('radio', { name: 'managed' }),
+ localFilter: byRole('radio', { name: 'local' }),
searchInput: byRole('searchbox', { name: 'search.search_by_name' }),
- updateButton: byRole('button', { name: 'update_details' }),
+ updateButton: byRole('menuitem', { name: 'update_details' }),
updateDialog: byRole('dialog', { name: 'groups.update_group' }),
updateDialogButton: byRole('button', { name: 'update_verb' }),
- deleteButton: byRole('button', { name: 'delete' }),
+ deleteButton: byRole('menuitem', { name: 'delete' }),
+ deleteIconButton: byRole('button', { name: /delete_x/ }),
deleteDialog: byRole('dialog', { name: 'groups.delete_group' }),
deleteDialogButton: byRole('button', { name: 'delete' }),
showMore: byRole('button', { name: 'show_more' }),
- nameInput: byRole('textbox', { name: 'name field_required' }),
+ nameInput: byRole('textbox', { name: 'name required' }),
descriptionInput: byRole('textbox', { name: 'description' }),
createGroupDialogButton: byRole('button', { name: 'create' }),
editGroupDialogButton: byRole('button', { name: 'groups.create_group' }),
expect(await ui.localGroupRowWithLocalBadge.find()).toBeInTheDocument();
await user.click(await ui.localFilter.find());
- await user.click(await ui.localEditButton.find());
-
- expect(ui.updateButton.query()).not.toBeInTheDocument();
+ expect(ui.localEditButton.query()).not.toBeInTheDocument();
+ expect(await ui.localGroupRowWithLocalBadge.by(ui.deleteIconButton).find()).toBeInTheDocument();
- await user.click(await ui.deleteButton.find());
+ await user.click(ui.localGroupRowWithLocalBadge.by(ui.deleteIconButton).get());
expect(await ui.deleteDialog.find()).toBeInTheDocument();
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { DangerButtonPrimary, Modal } from 'design-system';
import * as React from 'react';
-import SimpleModal from '../../../components/controls/SimpleModal';
-import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
-import Spinner from '../../../components/ui/Spinner';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { useDeleteGroupMutation } from '../../../queries/groups';
import { Group } from '../../../types/types';
onClose: () => void;
}
-export default function DeleteGroupForm(props: Props) {
+export default function DeleteGroupForm(props: Readonly<Props>) {
const { group } = props;
- const { mutate: deleteGroup } = useDeleteGroupMutation();
+ const { mutate: deleteGroup, isLoading } = useDeleteGroupMutation();
const onSubmit = () => {
deleteGroup(group.id, {
});
};
- const header = translate('groups.delete_group');
return (
- <SimpleModal header={header} onClose={props.onClose} onSubmit={onSubmit}>
- {({ onCloseClick, onFormSubmit, submitting }) => (
- <form onSubmit={onFormSubmit}>
- <header className="modal-head">
- <h2>{header}</h2>
- </header>
-
- <div className="modal-body">
- {translateWithParameters('groups.delete_group.confirmation', group.name)}
- </div>
-
- <footer className="modal-foot">
- <Spinner className="spacer-right" loading={submitting} />
- <SubmitButton className="button-red" disabled={submitting}>
- {translate('delete')}
- </SubmitButton>
- <ResetButtonLink disabled={submitting} onClick={onCloseClick}>
- {translate('cancel')}
- </ResetButtonLink>
- </footer>
- </form>
- )}
- </SimpleModal>
+ <Modal
+ headerTitle={translate('groups.delete_group')}
+ onClose={props.onClose}
+ body={translateWithParameters('groups.delete_group.confirmation', group.name)}
+ primaryButton={
+ <DangerButtonPrimary autoFocus type="submit" onClick={onSubmit} disabled={isLoading}>
+ {translate('delete')}
+ </DangerButtonPrimary>
+ }
+ secondaryButtonLabel={translate('cancel')}
+ />
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Modal, TextMuted } from 'design-system';
import { find } from 'lodash';
import * as React from 'react';
-import Modal from '../../../components/controls/Modal';
import SelectList, {
SelectListFilter,
SelectListSearchParams,
} from '../../../components/controls/SelectList';
-import { ResetButtonLink } from '../../../components/controls/buttons';
import { translate } from '../../../helpers/l10n';
import {
useAddGroupMembershipMutation,
}
return (
- <div className="select-list-list-item">
+ <div>
{user.name}
<br />
- <span className="note">{user.login}</span>
+ <TextMuted text={user.login} />
</div>
);
};
return (
<Modal
- className="group-menbers-modal"
- contentLabel={modalHeader}
- onRequestClose={props.onClose}
- >
- <header className="modal-head">
- <h2>{modalHeader}</h2>
- </header>
-
- <div className="modal-body modal-container">
+ headerTitle={modalHeader}
+ body={
<SelectList
elements={users.map((user) => user.id)}
elementsTotalCount={data?.pages[0].page.total}
withPaging
loading={isLoading}
/>
- </div>
-
- <footer className="modal-foot">
- <ResetButtonLink onClick={props.onClose}>{translate('done')}</ResetButtonLink>
- </footer>
- </Modal>
+ }
+ secondaryButtonLabel={translate('done')}
+ onClose={props.onClose}
+ />
);
}
* 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, FormField, InputField, InputTextArea, Modal } from 'design-system';
import * as React from 'react';
import { useState } from 'react';
-import SimpleModal from '../../../components/controls/SimpleModal';
-import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
-import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker';
import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
-import Spinner from '../../../components/ui/Spinner';
import { translate } from '../../../helpers/l10n';
import { useCreateGroupMutation, useUpdateGroupMutation } from '../../../queries/groups';
import { Group } from '../../../types/types';
const [name, setName] = useState<string>(create ? '' : group.name);
const [description, setDescription] = useState<string>(create ? '' : group.description ?? '');
- const { mutate: createGroup } = useCreateGroupMutation();
- const { mutate: updateGroup } = useUpdateGroupMutation();
+ const { mutate: createGroup, isLoading: isCreating } = useCreateGroupMutation();
+ const { mutate: updateGroup, isLoading: isUpdating } = useUpdateGroupMutation();
const handleCreateGroup = () => {
createGroup({ name, description }, { onSuccess: props.onClose });
};
return (
- <SimpleModal
- header={create ? translate('groups.create_group') : translate('groups.update_group')}
+ <Modal
+ headerTitle={create ? translate('groups.create_group') : translate('groups.update_group')}
+ body={
+ <>
+ <MandatoryFieldsExplanation className="sw-block sw-mb-4" />
+ <FormField htmlFor="create-group-name" label={translate('name')} required>
+ <InputField
+ autoFocus
+ id="create-group-name"
+ maxLength={255}
+ name="name"
+ onChange={(event: React.SyntheticEvent<HTMLInputElement>) => {
+ setName(event.currentTarget.value);
+ }}
+ required
+ size="full"
+ type="text"
+ value={name}
+ />
+ </FormField>
+ <FormField htmlFor="create-group-description" label={translate('description')}>
+ <InputTextArea
+ id="create-group-description"
+ name="description"
+ onChange={(event: React.SyntheticEvent<HTMLTextAreaElement>) => {
+ setDescription(event.currentTarget.value);
+ }}
+ size="full"
+ value={description}
+ />
+ </FormField>
+ </>
+ }
onClose={props.onClose}
- onSubmit={create ? handleCreateGroup : handleUpdateGroup}
- size="small"
- >
- {({ onCloseClick, onFormSubmit, submitting }) => (
- <form onSubmit={onFormSubmit}>
- <header className="modal-head">
- <h2>{create ? translate('groups.create_group') : translate('groups.update_group')}</h2>
- </header>
-
- <div className="modal-body">
- <MandatoryFieldsExplanation className="modal-field" />
- <div className="modal-field">
- <label htmlFor="create-group-name">
- {translate('name')}
- <MandatoryFieldMarker />
- </label>
- <input
- autoFocus
- id="create-group-name"
- maxLength={255}
- name="name"
- onChange={(event: React.SyntheticEvent<HTMLInputElement>) => {
- setName(event.currentTarget.value);
- }}
- required
- size={50}
- type="text"
- value={name}
- />
- </div>
- <div className="modal-field">
- <label htmlFor="create-group-description">{translate('description')}</label>
- <textarea
- id="create-group-description"
- name="description"
- onChange={(event: React.SyntheticEvent<HTMLTextAreaElement>) => {
- setDescription(event.currentTarget.value);
- }}
- value={description}
- />
- </div>
- </div>
-
- <footer className="modal-foot">
- <Spinner className="spacer-right" loading={submitting} />
- <SubmitButton disabled={submitting}>
- {create ? translate('create') : translate('update_verb')}
- </SubmitButton>
- <ResetButtonLink onClick={onCloseClick}>{translate('cancel')}</ResetButtonLink>
- </footer>
- </form>
- )}
- </SimpleModal>
+ primaryButton={
+ <ButtonPrimary
+ disabled={isUpdating || isCreating || name === ''}
+ onClick={create ? handleCreateGroup : handleUpdateGroup}
+ >
+ {create ? translate('create') : translate('update_verb')}
+ </ButtonPrimary>
+ }
+ secondaryButtonLabel={translate('cancel')}
+ />
);
}
* 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, 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 DocumentationLink from '../../../components/common/DocumentationLink';
import { translate } from '../../../helpers/l10n';
import { Provider } from '../../../types/types';
import GroupForm from './GroupForm';
return (
<>
- <div className="page-header null-spacer-bottom" id="groups-header">
- <h2 className="page-title">{translate('user_groups.page')}</h2>
-
- <div className="page-actions">
- <Button
+ <div id="groups-header">
+ <div className="sw-flex sw-justify-between">
+ <Title className="sw-mb-4">{translate('user_groups.page')}</Title>
+ <ButtonPrimary
id="groups-create"
disabled={manageProvider !== undefined}
onClick={() => setCreateModal(true)}
>
{translate('groups.create_group')}
- </Button>
+ </ButtonPrimary>
</div>
{manageProvider === undefined ? (
- <p className="page-description">{translate('user_groups.page.description')}</p>
+ <p className="sw-mb-4">{translate('user_groups.page.description')}</p>
) : (
- <Alert className="page-description max-width-100 width-100" variant="info">
- <FormattedMessage
- defaultMessage={translate('user_groups.page.managed_description')}
- id="user_groups.page.managed_description"
- values={{
- provider: manageProvider,
- link: (
- <DocLink to="/instance-administration/authentication/overview/">
- {translate('documentation')}
- </DocLink>
- ),
- }}
- />
- </Alert>
+ <FlagMessage className="sw-mb-4 sw-max-w-full sw-w-full" variant="info">
+ <div>
+ <FormattedMessage
+ defaultMessage={translate('user_groups.page.managed_description')}
+ id="user_groups.page.managed_description"
+ values={{
+ provider: manageProvider,
+ link: (
+ <DocumentationLink to="/instance-administration/authentication/overview/">
+ {translate('documentation')}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ </div>
+ </FlagMessage>
)}
</div>
{createModal && <GroupForm onClose={() => setCreateModal(false)} create />}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ContentCell, NumericalCell, Table, TableRow } from 'design-system';
import { sortBy } from 'lodash';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
manageProvider: Provider | undefined;
}
-export default function List(props: Props) {
+function Header() {
+ return (
+ <TableRow>
+ <ContentCell>{translate('user_groups.page.group_header')}</ContentCell>
+ <NumericalCell>{translate('members')}</NumericalCell>
+ <ContentCell>{translate('description')}</ContentCell>
+ <NumericalCell>{translate('actions')}</NumericalCell>
+ </TableRow>
+ );
+}
+
+export default function List(props: Readonly<Props>) {
const { groups, manageProvider } = props;
return (
- <div className="boxed-group boxed-group-inner">
- <table className="data zebra zebra-hover" id="groups-list">
- <thead>
- <tr>
- <th id="list-group-name">{translate('user_groups.page.group_header')}</th>
- <th id="list-group-member" className="nowrap width-10">
- {translate('members')}
- </th>
- <th id="list-group-description" className="nowrap">
- {translate('description')}
- </th>
- <th id="list-group-actions">{translate('actions')}</th>
- </tr>
- </thead>
- <tbody>
- {sortBy(groups, (group) => group.name.toLowerCase()).map((group) => (
- <ListItem group={group} key={group.name} manageProvider={manageProvider} />
- ))}
- </tbody>
- </table>
- </div>
+ <Table columnCount={4} header={<Header />} id="groups-list">
+ {sortBy(groups, (group) => group.name.toLowerCase()).map((group) => (
+ <ListItem group={group} key={group.name} manageProvider={manageProvider} />
+ ))}
+ </Table>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Spinner } from 'design-system';
+import {
+ ActionsDropdown,
+ Badge,
+ ContentCell,
+ DestructiveIcon,
+ ItemButton,
+ ItemDangerButton,
+ ItemDivider,
+ NumericalCell,
+ PopupZLevel,
+ Spinner,
+ TableRow,
+ TrashIcon,
+} from 'design-system';
import * as React from 'react';
import { useState } from 'react';
-import ActionsDropdown, {
- ActionsDropdownDivider,
- ActionsDropdownItem,
-} from '../../../components/controls/ActionsDropdown';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system';
import { useGroupMembersCountQuery } from '../../../queries/group-memberships';
manageProvider: Provider | undefined;
}
-export default function ListItem(props: ListItemProps) {
+export default function ListItem(props: Readonly<ListItemProps>) {
const { manageProvider, group } = props;
const { name, managed, description } = group;
return (
<img
alt={identityProvider}
- className="spacer-left spacer-right"
+ className="sw-ml-2 sw-mr-2"
height={16}
src={`${getBaseUrl()}/images/alm/${identityProvider}.svg`}
/>
};
return (
- <tr data-id={name}>
- <td className="width-20" headers="list-group-name">
- <b>{name}</b>
- {group.default && <span className="little-spacer-left">({translate('default')})</span>}
+ <TableRow data-id={name}>
+ <ContentCell>
+ <div className="sw-body-sm-highlight">{name}</div>
+ {group.default && <span className="sw-ml-1">({translate('default')})</span>}
{managed && renderIdentityProviderIcon(manageProvider)}
- {isGroupLocal() && <span className="little-spacer-left badge">{translate('local')}</span>}
- </td>
+ {isGroupLocal() && <Badge className="sw-ml-1">{translate('local')}</Badge>}
+ </ContentCell>
- <td className="group-members display-flex-justify-end" headers="list-group-member">
- <Spinner loading={isLoading}>
- <span>{membersCount}</span>
- </Spinner>
+ <NumericalCell>
+ <Spinner loading={isLoading}>{membersCount}</Spinner>
<Members group={group} onEdit={refetch} isManaged={isManaged()} />
- </td>
+ </NumericalCell>
- <td className="width-40" headers="list-group-description">
- <span className="js-group-description">{description}</span>
- </td>
+ <ContentCell>{description}</ContentCell>
- <td className="thin nowrap text-right" headers="list-group-actions">
+ <NumericalCell>
{!group.default && (!isManaged() || isGroupLocal()) && (
- <ActionsDropdown label={translateWithParameters('groups.edit', group.name)}>
- {!isManaged() && (
- <>
- <ActionsDropdownItem
- className="js-group-update"
- onClick={() => setGroupToEdit(group)}
- >
- {translate('update_details')}
- </ActionsDropdownItem>
- <ActionsDropdownDivider />
- </>
- )}
- {(!isManaged() || isGroupLocal()) && (
- <ActionsDropdownItem
- className="js-group-delete"
- destructive
+ <>
+ {isManaged() && isGroupLocal() && (
+ <DestructiveIcon
+ Icon={TrashIcon}
+ className="sw-ml-2"
+ aria-label={translateWithParameters('delete_x', name)}
onClick={() => setGroupToDelete(group)}
+ size="small"
+ />
+ )}
+ {!isManaged() && (
+ <ActionsDropdown
+ allowResizing
+ id={`group-actions-${group.name}`}
+ ariaLabel={translateWithParameters('groups.edit', group.name)}
+ zLevel={PopupZLevel.Global}
>
- {translate('delete')}
- </ActionsDropdownItem>
+ <ItemButton onClick={() => setGroupToEdit(group)}>
+ {translate('update_details')}
+ </ItemButton>
+ <ItemDivider />
+ <ItemDangerButton
+ className="it__quality-profiles__delete"
+ onClick={() => setGroupToDelete(group)}
+ >
+ {translate('delete')}
+ </ItemDangerButton>
+ </ActionsDropdown>
)}
- </ActionsDropdown>
+ </>
)}
{groupToDelete && (
<DeleteGroupForm group={groupToDelete} onClose={() => setGroupToDelete(undefined)} />
{groupToEdit && (
<GroupForm create={false} group={groupToEdit} onClose={() => setGroupToEdit(undefined)} />
)}
- </td>
- </tr>
+ </NumericalCell>
+ </TableRow>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { InteractiveIcon, MenuIcon, PencilIcon } from 'design-system';
import * as React from 'react';
-import { ButtonIcon } from '../../../components/controls/buttons';
-import BulletListIcon from '../../../components/icons/BulletListIcon';
import { translateWithParameters } from '../../../helpers/l10n';
import { Group } from '../../../types/types';
import EditMembersModal from './EditMembersModal';
}
};
+ const isReadonly = isManaged || group.default;
+
+ const title = translateWithParameters(
+ isReadonly ? 'groups.users.view' : 'groups.users.edit',
+ group.name,
+ );
+
return (
<>
- <ButtonIcon
- aria-label={translateWithParameters(
- isManaged || group.default ? 'groups.users.view' : 'groups.users.edit',
- group.name,
- )}
- className="button-small little-spacer-left little-padded"
+ <InteractiveIcon
+ Icon={isReadonly ? MenuIcon : PencilIcon}
+ className="sw-ml-2"
+ aria-label={title}
onClick={() => setOpenModal(true)}
- title={translateWithParameters('groups.users.edit', group.name)}
- >
- <BulletListIcon />
- </ButtonIcon>
+ size="small"
+ />
{openModal &&
- (isManaged || group.default ? (
+ (isReadonly ? (
<ViewMembersModal isManaged={isManaged} group={group} onClose={handleModalClose} />
) : (
<EditMembersModal group={group} onClose={handleModalClose} />
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Spinner } from 'design-system';
+import { Badge, InputSearch, Modal, Spinner, TextMuted } from 'design-system';
import * as React from 'react';
import ListFooter from '../../../components/controls/ListFooter';
-import Modal from '../../../components/controls/Modal';
-import SearchBox from '../../../components/controls/SearchBox';
-import { ResetButtonLink } from '../../../components/controls/buttons';
import { translate } from '../../../helpers/l10n';
import { useGroupMembersQuery } from '../../../queries/group-memberships';
import { Group } from '../../../types/types';
const modalHeader = translate('users.list');
return (
<Modal
- className="group-menbers-modal"
- contentLabel={modalHeader}
- onRequestClose={props.onClose}
- >
- <header className="modal-head">
- <h2>{modalHeader}</h2>
- </header>
-
- <div className="modal-body modal-container">
- <SearchBox
- className="view-search-box"
- loading={isLoading}
- onChange={setQuery}
- placeholder={translate('search_verb')}
- value={query}
- />
- <div className="select-list-list-container spacer-top sw-overflow-auto">
- <Spinner loading={isLoading}>
- <ul className="menu">
- {users.map((user) => (
- <li key={user.login} className="display-flex-center">
- <span className="little-spacer-left width-100">
- <span className="select-list-list-item display-flex-center display-flex-space-between">
- <span className="spacer-right">
- {user.name}
- <br />
- <span className="note">{user.login}</span>
+ headerTitle={modalHeader}
+ body={
+ <>
+ <InputSearch
+ className="sw-w-full sw-top-0 sw-sticky"
+ loading={isLoading}
+ onChange={setQuery}
+ placeholder={translate('search_verb')}
+ value={query}
+ />
+ <div className="sw-mt-6">
+ <Spinner loading={isLoading}>
+ <ul>
+ {users.map((user) => (
+ <li key={user.login} className="sw-flex sw-items-center">
+ <span className="sw-ml-1 sw-w-full">
+ <span className="sw-flex sw-justify-between sw-items-center">
+ <span className="sw-mr-2">
+ {user.name}
+ <br />
+ <TextMuted text={user.login} />
+ </span>
+ {!user.managed && isManaged && <Badge>{translate('local')}</Badge>}
</span>
- {!user.managed && isManaged && (
- <span className="badge">{translate('local')}</span>
- )}
</span>
- </span>
- </li>
- ))}
- </ul>
- </Spinner>
- </div>
- {data !== undefined && (
- <ListFooter
- count={users.length}
- loadMore={fetchNextPage}
- total={data?.pages[0].page.total}
- />
- )}
- </div>
-
- <footer className="modal-foot">
- <ResetButtonLink onClick={props.onClose}>{translate('done')}</ResetButtonLink>
- </footer>
- </Modal>
+ </li>
+ ))}
+ </ul>
+ </Spinner>
+ {data !== undefined && (
+ <ListFooter
+ count={users.length}
+ loadMore={fetchNextPage}
+ total={data?.pages[0].page.total}
+ useMIUIButtons
+ />
+ )}
+ </div>
+ </>
+ }
+ onClose={props.onClose}
+ />
);
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.
- */
-
-#groups-page .group-members {
- padding-right: 50%;
-}
-
-.group-menbers-modal .modal-container > :last-child {
- margin-bottom: 0;
-}
-
-.group-menbers-modal .select-list-list-container {
- height: 350px;
-}
-
-.group-menbers-modal .modal-body {
- padding: 12px 32px;
-}
-
-.group-members-modal .view-search-box.search-box {
- max-width: 100%;
-}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ToggleButton } from 'design-system';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
import { Provider } from '../../types/types';
manageProvider: Provider | undefined;
loading: boolean;
managed: boolean | undefined;
+ miui?: boolean;
setManaged: (managed: boolean | undefined) => void;
}
-export function ManagedFilter(props: ManagedFilterProps) {
- const { manageProvider, loading, managed } = props;
+export function ManagedFilter(props: Readonly<ManagedFilterProps>) {
+ const { manageProvider, loading, managed, miui } = props;
if (manageProvider === undefined) {
return null;
}
return (
- <div className="big-spacer-right">
- <ButtonToggle
- value={managed ?? 'all'}
- disabled={loading}
- options={[
- { label: translate('all'), value: 'all' },
- { label: translate('managed'), value: true },
- { label: translate('local'), value: false },
- ]}
- onCheck={(filterOption) => {
- if (filterOption === 'all') {
- props.setManaged(undefined);
- } else {
- props.setManaged(filterOption as boolean);
- }
- }}
- />
+ <div className="sw-mr-4">
+ {miui ? (
+ <ToggleButton
+ value={managed ?? 'all'}
+ disabled={loading}
+ options={[
+ { label: translate('all'), value: 'all' },
+ { label: translate('managed'), value: true },
+ { label: translate('local'), value: false },
+ ]}
+ onChange={(filterOption) => {
+ if (filterOption === 'all') {
+ props.setManaged(undefined);
+ } else {
+ props.setManaged(filterOption);
+ }
+ }}
+ />
+ ) : (
+ <ButtonToggle
+ value={managed ?? 'all'}
+ disabled={loading}
+ options={[
+ { label: translate('all'), value: 'all' },
+ { label: translate('managed'), value: true },
+ { label: translate('local'), value: false },
+ ]}
+ onCheck={(filterOption) => {
+ if (filterOption === 'all') {
+ props.setManaged(undefined);
+ } else {
+ props.setManaged(filterOption as boolean);
+ }
+ }}
+ />
+ )}
</div>
);
}
days=Days
default=Default
delete=Delete
+delete_x=Delete {0}
deprecated=Deprecated
descending=Descending
description=Description