className={classNames('alert', className)}
{...domProps}
>
- <div className="flag-inner">
- <div className="flag-icon">{variantInfo.icon}</div>
- <div className="flag-content">{props.children}</div>
- </div>
+ {props.children && (
+ <div className="flag-inner">
+ <div className="flag-icon">{variantInfo.icon}</div>
+ <div className="flag-content">{props.children}</div>
+ </div>
+ )}
</StyledFlag>
);
}
border: ${({ borderColor }) => themeBorder('default', borderColor)};
background-color: ${themeColor('flagMessageBackground')};
+ :empty {
+ display: none;
+ }
+
& > .flag-inner {
${tw`sw-flex sw-items-stretch`}
${tw`sw-box-border`}
*/
import styled from '@emotion/styled';
import { formatDistance } from 'date-fns';
-import { CheckIcon, FlagMessage, FlagWarningIcon, Link, themeColor } from 'design-system';
+import { CheckIcon, FlagMessage, FlagWarningIcon, Link, Spinner, themeColor } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
-import { Alert } from '../../components/ui/Alert';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { AlmSyncStatus } from '../../types/provisioning';
import { TaskStatuses } from '../../types/tasks';
</div>
) : (
<FlagMessage variant="error">
- <FormattedMessage
- id="settings.authentication.github.synchronization_failed_short"
- defaultMessage={translate('settings.authentication.github.synchronization_failed_short')}
- values={{
- details: (
- <Link className="sw-ml-2" to="/admin/settings?category=authentication&tab=github">
- {translate('settings.authentication.github.synchronization_details_link')}
- </Link>
- ),
- }}
- />
+ <div>
+ <FormattedMessage
+ id="settings.authentication.github.synchronization_failed_short"
+ defaultMessage={translate(
+ 'settings.authentication.github.synchronization_failed_short',
+ )}
+ values={{
+ details: (
+ <Link className="sw-ml-2" to="/admin/settings?category=authentication&tab=github">
+ {translate('settings.authentication.github.synchronization_details_link')}
+ </Link>
+ ),
+ }}
+ />
+ </div>
</FlagMessage>
);
}
return (
<>
- <Alert
+ <FlagMessage
variant={status === TaskStatuses.Success ? 'success' : 'error'}
role="alert"
aria-live="assertive"
>
- {status === TaskStatuses.Success ? (
- <>
- {translateWithParameters(
- 'settings.authentication.github.synchronization_successful',
- formattedDate,
- )}
- <br />
- {summary ?? ''}
- </>
- ) : (
- <React.Fragment key={`synch-alert-${finishedAt}`}>
- <div>
+ <div>
+ {status === TaskStatuses.Success ? (
+ <>
{translateWithParameters(
- 'settings.authentication.github.synchronization_failed',
+ 'settings.authentication.github.synchronization_successful',
formattedDate,
)}
- </div>
- <br />
- {errorMessage ?? ''}
- </React.Fragment>
- )}
- </Alert>
- <Alert variant="warning" role="alert" aria-live="assertive">
+ <br />
+ {summary ?? ''}
+ </>
+ ) : (
+ <React.Fragment key={`synch-alert-${finishedAt}`}>
+ <div>
+ {translateWithParameters(
+ 'settings.authentication.github.synchronization_failed',
+ formattedDate,
+ )}
+ </div>
+ <br />
+ {errorMessage ?? ''}
+ </React.Fragment>
+ )}
+ </div>
+ </FlagMessage>
+ <FlagMessage variant="warning" role="alert" aria-live="assertive">
{warningMessage}
- </Alert>
+ </FlagMessage>
</>
);
}
short,
data,
}: Readonly<SynchronisationWarningProps>) {
+ const loadingLabel =
+ data.nextSync &&
+ translate(
+ data.nextSync.status === TaskStatuses.Pending
+ ? 'settings.authentication.github.synchronization_pending'
+ : 'settings.authentication.github.synchronization_in_progress',
+ );
return (
<>
- <Alert
- variant="loading"
- className="spacer-bottom"
- aria-atomic
- role="alert"
- aria-live="assertive"
- aria-label={
- data.nextSync === undefined
- ? translate('settings.authentication.github.synchronization_finish')
- : ''
- }
- >
- {!short &&
- data?.nextSync &&
- translate(
- data.nextSync.status === TaskStatuses.Pending
- ? 'settings.authentication.github.synchronization_pending'
- : 'settings.authentication.github.synchronization_in_progress',
- )}
- </Alert>
+ {!short && (
+ <div className={data.nextSync ? 'sw-flex sw-gap-2 sw-mb-4' : ''}>
+ <Spinner loading={!!data.nextSync} ariaLabel={loadingLabel} />
+ <div>{data.nextSync && loadingLabel}</div>
+ </div>
+ )}
<LastSyncAlert short={short} info={data.lastSync} />
</>
definitions: ExtendedSettingDefinition[];
subCategory?: string;
displaySubCategoryTitle?: boolean;
+ noPadding?: boolean;
}
interface State {
}
render() {
- const { category, component, subCategory, displaySubCategoryTitle } = this.props;
+ const { category, component, subCategory, displaySubCategoryTitle, noPadding } = this.props;
const { settings } = this.state;
return (
settings={settings}
subCategory={subCategory}
displaySubCategoryTitle={displaySubCategoryTitle}
+ noPadding={noPadding}
/>
);
}
settings: Array<SettingDefinitionAndValue>;
subCategory?: string;
displaySubCategoryTitle?: boolean;
+ noPadding?: boolean;
}
class SubCategoryDefinitionsList extends React.PureComponent<SubCategoryDefinitionsListProps> {
};
render() {
- const { displaySubCategoryTitle = true, settings, subCategory, component } = this.props;
+ const {
+ displaySubCategoryTitle = true,
+ settings,
+ subCategory,
+ component,
+ noPadding,
+ } = this.props;
const bySubCategory = groupBy(settings, (setting) => setting.definition.subCategory);
const subCategories = Object.keys(bySubCategory).map((key) => ({
key,
return (
<ul>
{filteredSubCategories.map((subCategory, index) => (
- <li className="sw-p-6" key={subCategory.key}>
+ <li className={noPadding ? '' : 'sw-p-6'} key={subCategory.key}>
{displaySubCategoryTitle && (
<SubTitle
as="h3"
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
+import { FlagMessage, Link, SubTitle, ToggleButton } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { useSearchParams } from 'react-router-dom';
import withAvailableFeatures, {
WithAvailableFeaturesProps,
} from '../../../../app/components/available-features/withAvailableFeatures';
-import DocLink from '../../../../components/common/DocLink';
-import Link from '../../../../components/common/Link';
-import ScreenPositionHelper from '../../../../components/common/ScreenPositionHelper';
-import BoxedTabs, { getTabId, getTabPanelId } from '../../../../components/controls/BoxedTabs';
-import { Alert } from '../../../../components/ui/Alert';
+import DocumentationLink from '../../../../components/common/DocumentationLink';
+import { getTabId, getTabPanelId } from '../../../../components/controls/BoxedTabs';
import { translate } from '../../../../helpers/l10n';
import { getBaseUrl } from '../../../../helpers/system';
import { searchParamsToQuery } from '../../../../helpers/urls';
definitions: ExtendedSettingDefinition[];
}
-// We substract the footer height with padding (80) and the main layout padding (20)
-const HEIGHT_ADJUSTMENT = 100;
-
export type AuthenticationTabs =
| typeof SAML
| AlmKeys.GitHub
const tabs = [
{
- key: SAML,
+ value: SAML,
label: 'SAML',
},
{
- key: AlmKeys.GitHub,
+ value: AlmKeys.GitHub,
label: (
<>
{renderDevOpsIcon(AlmKeys.GitHub)}
),
},
{
- key: AlmKeys.BitbucketServer,
+ value: AlmKeys.BitbucketServer,
label: (
<>
{renderDevOpsIcon(AlmKeys.BitbucketServer)}
),
},
{
- key: AlmKeys.GitLab,
+ value: AlmKeys.GitLab,
label: (
<>
{renderDevOpsIcon(AlmKeys.GitLab)}
return (
<>
- <header className="page-header">
- <h3 className="page-title h2">{translate('settings.authentication.title')}</h3>
- </header>
+ <SubTitle as="h3">{translate('settings.authentication.title')}</SubTitle>
{props.hasFeature(Feature.LoginMessage) && (
- <Alert variant="info">
- <FormattedMessage
- id="settings.authentication.custom_message_information"
- defaultMessage={translate('settings.authentication.custom_message_information')}
- values={{
- link: (
- <Link to="/admin/settings?category=general#sonar.login.message">
- {translate('settings.authentication.custom_message_information.link')}
- </Link>
- ),
- }}
- />
- </Alert>
+ <FlagMessage variant="info">
+ <div>
+ <FormattedMessage
+ id="settings.authentication.custom_message_information"
+ defaultMessage={translate('settings.authentication.custom_message_information')}
+ values={{
+ link: (
+ <Link to="/admin/settings?category=general#sonar.login.message">
+ {translate('settings.authentication.custom_message_information.link')}
+ </Link>
+ ),
+ }}
+ />
+ </div>
+ </FlagMessage>
)}
- <div className="big-spacer-top huge-spacer-bottom">
+ <div className="sw-my-6">
<p>{translate('settings.authentication.description')}</p>
</div>
- <BoxedTabs
- onSelect={(tab: AuthenticationTabs) => {
+ <ToggleButton
+ role="tablist"
+ onChange={(tab: AuthenticationTabs) => {
setSearchParams({ ...searchParamsToQuery(query), tab });
}}
- selected={currentTab}
- tabs={tabs}
+ value={currentTab}
+ options={tabs}
/>
- {/* Adding a key to force re-rendering of the tab container, so that it resets the scroll position */}
- <ScreenPositionHelper>
- {({ top }) => (
- <>
- {tabs.map((tab) => (
- <div
- style={{
- maxHeight:
- tab.key === AlmKeys.BitbucketServer
- ? `calc(100vh - ${top + HEIGHT_ADJUSTMENT}px)`
- : '',
- }}
- className={classNames('bordered overflow-y-auto tabbed-definitions', {
- hidden: currentTab !== tab.key,
- })}
- key={tab.key}
- role="tabpanel"
- aria-labelledby={getTabId(tab.key)}
- id={getTabPanelId(tab.key)}
- >
- {currentTab === tab.key && (
- <div className="big-padded-top big-padded-left big-padded-right">
- {tab.key === SAML && <SamlAuthenticationTab definitions={samlDefinitions} />}
-
- {tab.key === AlmKeys.GitHub && (
- <GithubAuthenticationTab
- currentTab={currentTab}
- definitions={githubDefinitions}
+ {tabs.map((tab) => (
+ <div
+ className={classNames('sw-overflow-y-auto', {
+ hidden: currentTab !== tab.value,
+ })}
+ key={tab.value}
+ role="tabpanel"
+ aria-labelledby={getTabId(tab.value)}
+ id={getTabPanelId(tab.value)}
+ >
+ {currentTab === tab.value && (
+ <div className="sw-mt-6">
+ {tab.value === SAML && <SamlAuthenticationTab definitions={samlDefinitions} />}
+
+ {tab.value === AlmKeys.GitHub && (
+ <GithubAuthenticationTab currentTab={currentTab} definitions={githubDefinitions} />
+ )}
+
+ {tab.value === AlmKeys.GitLab && <GitLabAuthenticationTab />}
+
+ {tab.value === AlmKeys.BitbucketServer && (
+ <>
+ <FlagMessage variant="info">
+ <div>
+ <FormattedMessage
+ id="settings.authentication.help"
+ defaultMessage={translate('settings.authentication.help')}
+ values={{
+ link: (
+ <DocumentationLink
+ to={`/instance-administration/authentication/${
+ DOCUMENTATION_LINK_SUFFIXES[tab.value]
+ }/`}
+ >
+ {translate('settings.authentication.help.link')}
+ </DocumentationLink>
+ ),
+ }}
/>
- )}
-
- {tab.key === AlmKeys.GitLab && <GitLabAuthenticationTab />}
-
- {tab.key === AlmKeys.BitbucketServer && (
- <>
- <Alert variant="info">
- <FormattedMessage
- id="settings.authentication.help"
- defaultMessage={translate('settings.authentication.help')}
- values={{
- link: (
- <DocLink
- to={`/instance-administration/authentication/${
- DOCUMENTATION_LINK_SUFFIXES[tab.key]
- }/`}
- >
- {translate('settings.authentication.help.link')}
- </DocLink>
- ),
- }}
- />
- </Alert>
- <CategoryDefinitionsList
- category={AUTHENTICATION_CATEGORY}
- definitions={definitions}
- subCategory={tab.key}
- displaySubCategoryTitle={false}
- />
- </>
- )}
- </div>
- )}
- </div>
- ))}
- </>
- )}
- </ScreenPositionHelper>
+ </div>
+ </FlagMessage>
+ <CategoryDefinitionsList
+ category={AUTHENTICATION_CATEGORY}
+ definitions={definitions}
+ subCategory={tab.value}
+ displaySubCategoryTitle={false}
+ noPadding
+ />
+ </>
+ )}
+ </div>
+ )}
+ </div>
+ ))}
</>
);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.
+ */
+import {
+ ButtonPrimary,
+ ButtonSecondary,
+ DangerButtonSecondary,
+ SubHeading,
+ Tooltip,
+} from 'design-system';
+import React, { ReactElement } from 'react';
+import { translate } from '../../../../helpers/l10n';
+
+interface Props {
+ title: string;
+ url: string | string[] | undefined;
+ canDisable: boolean;
+ onEdit: () => void;
+ onDelete: () => void;
+ onToggle: () => void;
+ extraActions?: ReactElement;
+ isDeleting: boolean;
+ enabled: boolean;
+}
+
+export default function ConfigurationDetails(props: Readonly<Props>) {
+ const { title, url, canDisable, onEdit, onDelete, onToggle, extraActions, isDeleting, enabled } =
+ props;
+
+ return (
+ <div className="sw-flex sw-mb-6 sw-justify-between">
+ <div className="sw-min-w-0">
+ <SubHeading as="h5" title={title} className="sw-truncate">
+ {title}
+ </SubHeading>
+ <p>{url}</p>
+ <Tooltip
+ overlay={!canDisable ? translate('settings.authentication.form.disable.tooltip') : null}
+ >
+ {enabled ? (
+ <ButtonSecondary className="sw-mt-4" onClick={onToggle} disabled={!canDisable}>
+ {translate('settings.authentication.form.disable')}
+ </ButtonSecondary>
+ ) : (
+ <ButtonPrimary className="sw-mt-4" onClick={onToggle} disabled={!canDisable}>
+ {translate('settings.authentication.form.enable')}
+ </ButtonPrimary>
+ )}
+ </Tooltip>
+ </div>
+ <div className="sw-flex sw-gap-2 sw-flex-nowrap sw-shrink-0">
+ {extraActions}
+ <ButtonSecondary onClick={onEdit}>
+ {translate('settings.authentication.form.edit')}
+ </ButtonSecondary>
+ <Tooltip
+ overlay={
+ enabled || isDeleting ? translate('settings.authentication.form.delete.tooltip') : null
+ }
+ >
+ <DangerButtonSecondary disabled={enabled || isDeleting} onClick={onDelete}>
+ {translate('settings.authentication.form.delete')}
+ </DangerButtonSecondary>
+ </Tooltip>
+ </div>
+ </div>
+ );
+}
* 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 { omitBy } from 'lodash';
import React, { FormEvent, useContext } from 'react';
import { FormattedMessage } from 'react-intl';
import GitLabSynchronisationWarning from '../../../../app/components/GitLabSynchronisationWarning';
import { AvailableFeaturesContext } from '../../../../app/components/available-features/AvailableFeaturesContext';
-import DocLink from '../../../../components/common/DocLink';
+import DocumentationLink from '../../../../components/common/DocumentationLink';
import ConfirmModal from '../../../../components/controls/ConfirmModal';
-import RadioCard from '../../../../components/controls/RadioCard';
-import Tooltip from '../../../../components/controls/Tooltip';
-import { Button, ResetButtonLink, SubmitButton } from '../../../../components/controls/buttons';
-import DeleteIcon from '../../../../components/icons/DeleteIcon';
-import EditIcon from '../../../../components/icons/EditIcon';
-import { Alert } from '../../../../components/ui/Alert';
-import Spinner from '../../../../components/ui/Spinner';
-import { translate } from '../../../../helpers/l10n';
+import { translate, translateWithParameters } from '../../../../helpers/l10n';
import { useIdentityProviderQuery } from '../../../../queries/identity-provider/common';
import {
useDeleteGitLabConfigurationMutation,
import { Provider } from '../../../../types/types';
import { DOCUMENTATION_LINK_SUFFIXES } from './Authentication';
import AuthenticationFormField from './AuthenticationFormField';
+import ConfigurationDetails from './ConfigurationDetails';
import GitLabConfigurationForm from './GitLabConfigurationForm';
import GitLabConfigurationValidity from './GitLabConfigurationValidity';
+import ProvisioningSection from './ProvisioningSection';
+import TabHeader from './TabHeader';
interface ChangesForm {
provisioningType?: GitLabConfigurationUpdateBody['provisioningType'];
return (
<Spinner loading={isLoadingList}>
- <div className="authentication-configuration">
- <div className="spacer-bottom display-flex-space-between display-flex-center">
- <h4>{translate('settings.authentication.gitlab.configuration')}</h4>
- {!configuration && (
- <div>
- <Button onClick={() => setOpenForm(true)}>
- {translate('settings.authentication.form.create')}
- </Button>
- </div>
- )}
- </div>
- {!isLoadingList && configuration?.enabled && (
- <GitLabConfigurationValidity
- configuration={configuration}
- loading={isFetching}
- onRecheck={refetch}
- />
- )}
+ <div>
+ <TabHeader
+ title={translate('settings.authentication.gitlab.configuration')}
+ showCreate={!configuration}
+ onCreate={() => setOpenForm(true)}
+ configurationValidity={
+ <>
+ {!isLoadingList && configuration?.enabled && (
+ <GitLabConfigurationValidity
+ configuration={configuration}
+ loading={isFetching}
+ onRecheck={refetch}
+ />
+ )}
+ </>
+ }
+ />
{!configuration && (
- <div className="big-padded text-center huge-spacer-bottom authentication-no-config">
- {translate('settings.authentication.gitlab.form.not_configured')}
- </div>
+ <div>{translate('settings.authentication.gitlab.form.not_configured')}</div>
)}
{configuration && (
- <div className="spacer-bottom big-padded bordered display-flex-space-between">
- <div>
- <p>{configuration.url}</p>
- <Tooltip
- overlay={
- configuration.provisioningType === ProvisioningType.auto
- ? translate('settings.authentication.form.disable.tooltip')
- : null
- }
- >
- <Button
- className="spacer-top"
- onClick={toggleEnable}
- disabled={isUpdating || configuration.provisioningType === ProvisioningType.auto}
- >
- {configuration.enabled
- ? translate('settings.authentication.form.disable')
- : translate('settings.authentication.form.enable')}
- </Button>
- </Tooltip>
- </div>
- <div>
- <Button className="spacer-right" onClick={() => setOpenForm(true)}>
- <EditIcon />
- {translate('settings.authentication.form.edit')}
- </Button>
- <Tooltip
- overlay={
- configuration.enabled
- ? translate('settings.authentication.form.delete.tooltip')
- : null
- }
- >
- <Button
- className="button-red"
- disabled={configuration.enabled || isDeleting}
- onClick={deleteConfiguration}
- >
- <DeleteIcon />
- {translate('settings.authentication.form.delete')}
- </Button>
- </Tooltip>
- </div>
- </div>
- )}
- {configuration && (
- <div className="spacer-bottom big-padded bordered">
- <form onSubmit={handleSubmit}>
- <fieldset className="display-flex-column big-spacer-bottom">
- <label className="h5">
- {translate('settings.authentication.form.provisioning')}
- </label>
-
- {configuration.enabled ? (
- <div className="display-flex-column spacer-top">
- <RadioCard
- className="sw-min-h-0"
- label={translate('settings.authentication.gitlab.provisioning_at_login')}
- title={translate('settings.authentication.gitlab.provisioning_at_login')}
- selected={provisioningType === ProvisioningType.jit}
- onClick={setJIT}
- >
- <p className="spacer-bottom">
- <FormattedMessage id="settings.authentication.gitlab.provisioning_at_login.description" />
- </p>
- <p className="spacer-bottom">
- <DocLink
- to={`/instance-administration/authentication/${
- DOCUMENTATION_LINK_SUFFIXES[AlmKeys.GitLab]
- }/#choosing-the-provisioning-method`}
- >
- {translate(
- `settings.authentication.gitlab.description.${ProvisioningType.jit}.learn_more`,
- )}
- </DocLink>
- </p>
- {provisioningType === ProvisioningType.jit &&
- allowUsersToSignUpDefinition !== undefined && (
- <AuthenticationFormField
- settingValue={allowUsersToSignUp}
- definition={allowUsersToSignUpDefinition}
- mandatory
- onFieldChange={(_, value) =>
- setChangesWithCheck({
- ...changes,
- allowUsersToSignUp: value as boolean,
- })
- }
- isNotSet={configuration.provisioningType !== ProvisioningType.auto}
- />
- )}
- </RadioCard>
- <RadioCard
- className="spacer-top sw-min-h-0"
- label={translate(
- 'settings.authentication.gitlab.form.provisioning_with_gitlab',
- )}
- title={translate(
- 'settings.authentication.gitlab.form.provisioning_with_gitlab',
- )}
- selected={provisioningType === ProvisioningType.auto}
- onClick={setAuto}
- disabled={!hasGitlabProvisioningFeature || hasDifferentProvider}
- >
- {hasGitlabProvisioningFeature ? (
- <>
- {hasDifferentProvider && (
- <p className="spacer-bottom text-bold ">
- {translate('settings.authentication.form.other_provisioning_enabled')}
- </p>
- )}
- <p className="spacer-bottom">
- {translate(
- 'settings.authentication.gitlab.form.provisioning_with_gitlab.description',
- )}
- </p>
- <p className="spacer-bottom">
- <DocLink
- to={`/instance-administration/authentication/${
- DOCUMENTATION_LINK_SUFFIXES[AlmKeys.GitLab]
- }/#choosing-the-provisioning-method`}
- >
- {translate(
- `settings.authentication.gitlab.description.${ProvisioningType.auto}.learn_more`,
- )}
- </DocLink>
- </p>
-
- {configuration.provisioningType === ProvisioningType.auto && (
- <GitLabSynchronisationWarning />
- )}
-
- {provisioningType === ProvisioningType.auto && (
- <>
- <div className="sw-flex sw-flex-1 spacer-bottom">
- <Button
- className="spacer-top width-30"
- onClick={synchronizeNow}
- disabled={!canSyncNow}
- >
- {translate('settings.authentication.github.synchronize_now')}
- </Button>
- </div>
- <hr />
- <AuthenticationFormField
- settingValue={provisioningToken}
- key={tokenKey}
- definition={provisioningTokenDefinition}
- mandatory
- onFieldChange={(_, value) =>
- setChangesWithCheck({
- ...changes,
- provisioningToken: value as string,
- })
- }
- isNotSet={!configuration.isProvisioningTokenSet}
- />
- </>
- )}
- </>
- ) : (
- <p>
- <FormattedMessage
- id="settings.authentication.gitlab.form.provisioning.disabled"
- defaultMessage={translate(
- 'settings.authentication.gitlab.form.provisioning.disabled',
- )}
- values={{
- documentation: (
- <DocLink to="/instance-administration/authentication/gitlab">
- {translate('documentation')}
- </DocLink>
- ),
- }}
- />
- </p>
- )}
- </RadioCard>
- </div>
- ) : (
- <Alert className="big-spacer-top" variant="info">
- {translate('settings.authentication.gitlab.enable_first')}
- </Alert>
- )}
- </fieldset>
- {configuration.enabled && (
- <div className="sw-flex sw-gap-2 sw-h-8 sw-items-center">
- <SubmitButton disabled={!canSave()}>{translate('save')}</SubmitButton>
- <ResetButtonLink
- onClick={() => {
- setChanges(undefined);
- setTokenKey(tokenKey + 1);
- }}
- disabled={false}
- >
- {translate('cancel')}
- </ResetButtonLink>
- <Alert variant="warning" className="sw-mb-0">
- {canSave() &&
- translate('settings.authentication.gitlab.configuration.unsaved_changes')}
- </Alert>
- <Spinner loading={isUpdating} />
- </div>
+ <>
+ <ConfigurationDetails
+ title={translateWithParameters(
+ 'settings.authentication.gitlab.applicationId.name',
+ configuration.applicationId,
)}
- {showConfirmProvisioningModal && provisioningType && (
- <ConfirmModal
- onConfirm={updateProvisioning}
- header={translate('settings.authentication.gitlab.confirm', provisioningType)}
- onClose={() => setShowConfirmProvisioningModal(false)}
- confirmButtonText={translate(
- 'settings.authentication.gitlab.provisioning_change.confirm_changes',
- )}
- >
- {translate(
- 'settings.authentication.gitlab.confirm',
- provisioningType,
- 'description',
+ url={configuration.url}
+ canDisable={!isUpdating && configuration.provisioningType !== ProvisioningType.auto}
+ enabled={configuration.enabled}
+ isDeleting={isDeleting}
+ onEdit={() => setOpenForm(true)}
+ onDelete={deleteConfiguration}
+ onToggle={toggleEnable}
+ />
+ <ProvisioningSection
+ provisioningType={provisioningType ?? ProvisioningType.jit}
+ onChangeProvisioningType={(val: ProvisioningType) =>
+ val === ProvisioningType.auto ? setAuto() : setJIT()
+ }
+ disabledConfigText={translate('settings.authentication.gitlab.enable_first')}
+ enabled={configuration.enabled}
+ hasUnsavedChanges={changes !== undefined}
+ canSave={canSave()}
+ onSave={handleSubmit}
+ onCancel={() => {
+ setChanges(undefined);
+ setTokenKey(tokenKey + 1);
+ }}
+ jitTitle={translate('settings.authentication.gitlab.provisioning_at_login')}
+ jitDescription={
+ <FormattedMessage
+ id="settings.authentication.gitlab.provisioning_at_login.description"
+ values={{
+ documentation: (
+ <DocumentationLink
+ to={`/instance-administration/authentication/${
+ DOCUMENTATION_LINK_SUFFIXES[AlmKeys.GitLab]
+ }/#choosing-the-provisioning-method`}
+ >
+ {translate(`learn_more`)}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ }
+ jitSettings={
+ <AuthenticationFormField
+ settingValue={allowUsersToSignUp}
+ definition={allowUsersToSignUpDefinition}
+ mandatory
+ onFieldChange={(_, value) =>
+ setChangesWithCheck({
+ ...changes,
+ allowUsersToSignUp: value as boolean,
+ })
+ }
+ isNotSet={configuration.provisioningType !== ProvisioningType.auto}
+ />
+ }
+ autoTitle={translate('settings.authentication.gitlab.form.provisioning_with_gitlab')}
+ hasDifferentProvider={hasDifferentProvider}
+ hasFeatureEnabled={hasGitlabProvisioningFeature}
+ autoFeatureDisabledText={
+ <FormattedMessage
+ id="settings.authentication.gitlab.form.provisioning.disabled"
+ defaultMessage={translate(
+ 'settings.authentication.gitlab.form.provisioning.disabled',
)}
- </ConfirmModal>
- )}
- </form>
- </div>
+ values={{
+ documentation: (
+ <DocumentationLink to="/instance-administration/authentication/gitlab">
+ {translate('documentation')}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ }
+ autoDescription={
+ <FormattedMessage
+ id="settings.authentication.gitlab.form.provisioning_with_gitlab.description"
+ values={{
+ documentation: (
+ <DocumentationLink
+ to={`/instance-administration/authentication/${
+ DOCUMENTATION_LINK_SUFFIXES[AlmKeys.GitLab]
+ }/#choosing-the-provisioning-method`}
+ >
+ {translate(`learn_more`)}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ }
+ onSyncNow={synchronizeNow}
+ canSync={canSyncNow}
+ synchronizationDetails={<GitLabSynchronisationWarning />}
+ autoSettings={
+ <AuthenticationFormField
+ settingValue={provisioningToken}
+ key={tokenKey}
+ definition={provisioningTokenDefinition}
+ mandatory
+ onFieldChange={(_, value) =>
+ setChangesWithCheck({
+ ...changes,
+ provisioningToken: value as string,
+ })
+ }
+ isNotSet={!configuration.isProvisioningTokenSet}
+ />
+ }
+ />
+ </>
)}
</div>
+ {showConfirmProvisioningModal && provisioningType && (
+ <ConfirmModal
+ onConfirm={updateProvisioning}
+ header={translate('settings.authentication.gitlab.confirm', provisioningType)}
+ onClose={() => setShowConfirmProvisioningModal(false)}
+ confirmButtonText={translate(
+ 'settings.authentication.gitlab.provisioning_change.confirm_changes',
+ )}
+ >
+ {translate('settings.authentication.gitlab.confirm', provisioningType, 'description')}
+ </ConfirmModal>
+ )}
{openForm && (
<GitLabConfigurationForm data={configuration ?? null} onClose={() => setOpenForm(false)} />
)}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ButtonSecondary, FlagMessage, Highlight, Note, Spinner } from 'design-system';
import React, { FormEvent, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import GitHubSynchronisationWarning from '../../../../app/components/GitHubSynchronisationWarning';
-import DocLink from '../../../../components/common/DocLink';
+import DocumentationLink from '../../../../components/common/DocumentationLink';
import ConfirmModal from '../../../../components/controls/ConfirmModal';
-import RadioCard from '../../../../components/controls/RadioCard';
-import Tooltip from '../../../../components/controls/Tooltip';
-import { Button, ResetButtonLink, SubmitButton } from '../../../../components/controls/buttons';
-import DeleteIcon from '../../../../components/icons/DeleteIcon';
-import EditIcon from '../../../../components/icons/EditIcon';
-import { Alert } from '../../../../components/ui/Alert';
-import Spinner from '../../../../components/ui/Spinner';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
import { useIdentityProviderQuery } from '../../../../queries/identity-provider/common';
import {
useSyncWithGitHubNow,
} from '../../../../queries/identity-provider/github';
import { AlmKeys } from '../../../../types/alm-settings';
+import { ProvisioningType } from '../../../../types/provisioning';
import { ExtendedSettingDefinition } from '../../../../types/settings';
import { Provider } from '../../../../types/types';
import { AuthenticationTabs, DOCUMENTATION_LINK_SUFFIXES } from './Authentication';
import AuthenticationFormField from './AuthenticationFormField';
-import AuthenticationFormFieldWrapper from './AuthenticationFormFieldWrapper';
import AutoProvisioningConsent from './AutoProvisionningConsent';
+import ConfigurationDetails from './ConfigurationDetails';
import ConfigurationForm from './ConfigurationForm';
import GitHubConfigurationValidity from './GitHubConfigurationValidity';
import GitHubMappingModal from './GitHubMappingModal';
+import ProvisioningSection from './ProvisioningSection';
+import TabHeader from './TabHeader';
import useGithubConfiguration, {
GITHUB_ADDITIONAL_FIELDS,
GITHUB_JIT_FIELDS,
}
};
+ const handleProvisioningTypeChange = (type: ProvisioningType) => {
+ setProvisioningType(type === ProvisioningType.auto);
+ };
+
return (
<Spinner loading={isLoading}>
- <div className="authentication-configuration">
- <div className="spacer-bottom display-flex-space-between display-flex-center">
- <h4>{translate('settings.authentication.github.configuration')}</h4>
-
- {!hasConfiguration && (
- <div>
- <Button onClick={handleCreateConfiguration}>
- {translate('settings.authentication.form.create')}
- </Button>
- </div>
- )}
- </div>
- {enabled && !hasLegacyConfiguration && (
- <GitHubConfigurationValidity
- selectedOrganizations={
- (values['sonar.auth.github.organizations']?.value as string[]) ?? []
- }
- isAutoProvisioning={!!(newGithubProvisioningStatus ?? githubProvisioningStatus)}
- />
- )}
+ <div>
+ <TabHeader
+ title={translate('settings.authentication.github.configuration')}
+ showCreate={!hasConfiguration}
+ onCreate={handleCreateConfiguration}
+ configurationValidity={
+ <>
+ {enabled && !hasLegacyConfiguration && (
+ <GitHubConfigurationValidity
+ selectedOrganizations={
+ (values['sonar.auth.github.organizations']?.value as string[]) ?? []
+ }
+ isAutoProvisioning={!!(newGithubProvisioningStatus ?? githubProvisioningStatus)}
+ />
+ )}
+ </>
+ }
+ />
{!hasConfiguration && !hasLegacyConfiguration && (
- <div className="big-padded text-center huge-spacer-bottom authentication-no-config">
- {translate('settings.authentication.github.form.not_configured')}
- </div>
+ <div>{translate('settings.authentication.github.form.not_configured')}</div>
)}
{!hasConfiguration && hasLegacyConfiguration && (
- <div className="big-padded">
- <Alert variant="warning">
+ <FlagMessage variant="warning">
+ <div>
<FormattedMessage
id="settings.authentication.github.form.legacy_configured"
defaultMessage={translate('settings.authentication.github.form.legacy_configured')}
values={{
documentation: (
- <DocLink to="/instance-administration/authentication/github">
+ <DocumentationLink to="/instance-administration/authentication/github">
{translate('settings.authentication.github.form.legacy_configured.link')}
- </DocLink>
+ </DocumentationLink>
),
}}
/>
- </Alert>
- </div>
+ </div>
+ </FlagMessage>
)}
{hasConfiguration && (
<>
- <div className="spacer-bottom big-padded bordered display-flex-space-between">
- <div>
- <h5>{translateWithParameters('settings.authentication.github.appid_x', appId)}</h5>
- <p>{url}</p>
- <Tooltip
- overlay={
- githubProvisioningStatus
- ? translate('settings.authentication.form.disable.tooltip')
- : null
- }
- >
- <Button
- className="spacer-top"
- onClick={toggleEnable}
- disabled={githubProvisioningStatus}
- >
- {enabled
- ? translate('settings.authentication.form.disable')
- : translate('settings.authentication.form.enable')}
- </Button>
- </Tooltip>
- </div>
- <div>
- <Button className="spacer-right" onClick={handleCreateConfiguration}>
- <EditIcon />
- {translate('settings.authentication.form.edit')}
- </Button>
- <Tooltip
- overlay={
- enabled || isDeleting
- ? translate('settings.authentication.form.delete.tooltip')
- : null
- }
- >
- <Button
- className="button-red"
- disabled={enabled || isDeleting}
- onClick={deleteConfiguration}
- >
- <DeleteIcon />
- {translate('settings.authentication.form.delete')}
- </Button>
- </Tooltip>
- </div>
- </div>
- <div className="spacer-bottom big-padded bordered display-flex-space-between">
- <form onSubmit={handleSubmit}>
- <fieldset className="display-flex-column big-spacer-bottom">
- <label className="h5">
- {translate('settings.authentication.form.provisioning')}
- </label>
+ <ConfigurationDetails
+ title={translateWithParameters('settings.authentication.github.appid_x', appId)}
+ url={url}
+ canDisable={!githubProvisioningStatus}
+ enabled={enabled}
+ isDeleting={isDeleting}
+ onEdit={handleCreateConfiguration}
+ onDelete={deleteConfiguration}
+ onToggle={toggleEnable}
+ />
- {enabled ? (
- <div className="display-flex-column spacer-top">
- <RadioCard
- className="sw-min-h-0"
- label={translate('settings.authentication.form.provisioning_at_login')}
- title={translate('settings.authentication.form.provisioning_at_login')}
- selected={!(newGithubProvisioningStatus ?? githubProvisioningStatus)}
- onClick={() => setProvisioningType(false)}
+ <ProvisioningSection
+ provisioningType={
+ newGithubProvisioningStatus ?? githubProvisioningStatus
+ ? ProvisioningType.auto
+ : ProvisioningType.jit
+ }
+ onChangeProvisioningType={handleProvisioningTypeChange}
+ disabledConfigText={translate('settings.authentication.github.enable_first')}
+ enabled={enabled}
+ hasUnsavedChanges={!!hasGithubProvisioningConfigChange}
+ onSave={handleSubmit}
+ onCancel={() => {
+ setProvisioningType(undefined);
+ resetJitSetting();
+ }}
+ jitTitle={translate('settings.authentication.form.provisioning_at_login')}
+ jitDescription={
+ <FormattedMessage
+ id="settings.authentication.github.form.provisioning_at_login.description"
+ values={{
+ documentation: (
+ <DocumentationLink
+ to={`/instance-administration/authentication/${
+ DOCUMENTATION_LINK_SUFFIXES[AlmKeys.GitHub]
+ }/`}
>
- <p className="spacer-bottom">
- <FormattedMessage id="settings.authentication.github.form.provisioning_at_login.description" />
- </p>
- <p className="spacer-bottom">
- <FormattedMessage
- id="settings.authentication.github.form.description.doc"
- values={{
- documentation: (
- <DocLink
- to={`/instance-administration/authentication/${
- DOCUMENTATION_LINK_SUFFIXES[AlmKeys.GitHub]
- }/`}
- >
- {translate('documentation')}
- </DocLink>
- ),
- }}
- />
- </p>
-
- {!(newGithubProvisioningStatus ?? githubProvisioningStatus) && (
- <>
- <hr />
- {Object.values(values).map((val) => {
- if (!GITHUB_JIT_FIELDS.includes(val.key)) {
- return null;
- }
- return (
- <div key={val.key} className="sw-mb-8">
- <AuthenticationFormField
- settingValue={
- values[val.key]?.newValue ?? values[val.key]?.value
- }
- definition={val.definition}
- mandatory={val.mandatory}
- onFieldChange={setNewValue}
- isNotSet={val.isNotSet}
- />
- </div>
- );
- })}
- </>
- )}
- </RadioCard>
- <RadioCard
- className="spacer-top sw-min-h-0"
- label={translate(
- 'settings.authentication.github.form.provisioning_with_github',
- )}
- title={translate(
- 'settings.authentication.github.form.provisioning_with_github',
+ {translate('learn_more')}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ }
+ jitSettings={
+ <>
+ {Object.values(values).map((val) => {
+ if (!GITHUB_JIT_FIELDS.includes(val.key)) {
+ return null;
+ }
+ return (
+ <AuthenticationFormField
+ key={val.key}
+ settingValue={values[val.key]?.newValue ?? values[val.key]?.value}
+ definition={val.definition}
+ mandatory={val.mandatory}
+ onFieldChange={setNewValue}
+ isNotSet={val.isNotSet}
+ />
+ );
+ })}
+ </>
+ }
+ autoTitle={translate('settings.authentication.github.form.provisioning_with_github')}
+ hasDifferentProvider={hasDifferentProvider}
+ hasFeatureEnabled={hasGithubProvisioning}
+ autoFeatureDisabledText={
+ <FormattedMessage
+ id="settings.authentication.github.form.provisioning.disabled"
+ defaultMessage={translate(
+ 'settings.authentication.github.form.provisioning.disabled',
+ )}
+ values={{
+ documentation: (
+ <DocumentationLink to="/instance-administration/authentication/github">
+ {translate('documentation')}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ }
+ autoDescription={
+ <FormattedMessage
+ id="settings.authentication.github.form.provisioning_with_github.description"
+ values={{
+ documentation: (
+ <DocumentationLink
+ to={`/instance-administration/authentication/${
+ DOCUMENTATION_LINK_SUFFIXES[AlmKeys.GitHub]
+ }/`}
+ >
+ {translate('learn_more')}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ }
+ synchronizationDetails={<GitHubSynchronisationWarning />}
+ onSyncNow={synchronizeNow}
+ canSync={canSyncNow}
+ autoSettings={
+ <>
+ {Object.values(values).map((val) => {
+ if (!GITHUB_PROVISIONING_FIELDS.includes(val.key)) {
+ return null;
+ }
+ return (
+ <div key={val.key}>
+ <AuthenticationFormField
+ settingValue={values[val.key]?.newValue ?? values[val.key]?.value}
+ definition={val.definition}
+ mandatory={val.mandatory}
+ onFieldChange={setNewValue}
+ isNotSet={val.isNotSet}
+ />
+ </div>
+ );
+ })}
+ <div className="sw-mt-6">
+ <div className="sw-flex">
+ <Highlight className="sw-mb-4 sw-mr-4 sw-flex sw-items-center sw-gap-2">
+ {translate(
+ 'settings.authentication.github.configuration.roles_mapping.title',
)}
- selected={newGithubProvisioningStatus ?? githubProvisioningStatus}
- onClick={() => setProvisioningType(true)}
- disabled={!hasGithubProvisioning || hasDifferentProvider}
+ </Highlight>
+ <ButtonSecondary
+ className="sw--mt-2"
+ onClick={() => setShowMappingModal(true)}
>
- {hasGithubProvisioning ? (
- <>
- {hasDifferentProvider && (
- <p className="spacer-bottom text-bold ">
- {translate(
- 'settings.authentication.form.other_provisioning_enabled',
- )}
- </p>
- )}
- <p className="spacer-bottom">
- {translate(
- 'settings.authentication.github.form.provisioning_with_github.description',
- )}
- </p>
- <p className="spacer-bottom">
- <FormattedMessage
- id="settings.authentication.github.form.description.doc"
- values={{
- documentation: (
- <DocLink
- to={`/instance-administration/authentication/${
- DOCUMENTATION_LINK_SUFFIXES[AlmKeys.GitHub]
- }/`}
- >
- {translate('documentation')}
- </DocLink>
- ),
- }}
- />
- </p>
-
- {githubProvisioningStatus && <GitHubSynchronisationWarning />}
- {(newGithubProvisioningStatus ?? githubProvisioningStatus) && (
- <>
- <div className="sw-flex sw-flex-1 spacer-bottom">
- <Button
- className="spacer-top width-30"
- onClick={synchronizeNow}
- disabled={!canSyncNow}
- >
- {translate('settings.authentication.github.synchronize_now')}
- </Button>
- </div>
- <hr />
- {Object.values(values).map((val) => {
- if (!GITHUB_PROVISIONING_FIELDS.includes(val.key)) {
- return null;
- }
- return (
- <div key={val.key}>
- <AuthenticationFormField
- settingValue={
- values[val.key]?.newValue ?? values[val.key]?.value
- }
- definition={val.definition}
- mandatory={val.mandatory}
- onFieldChange={setNewValue}
- isNotSet={val.isNotSet}
- />
- </div>
- );
- })}
- <AuthenticationFormFieldWrapper
- title={translate(
- 'settings.authentication.github.configuration.roles_mapping.title',
- )}
- description={translate(
- 'settings.authentication.github.configuration.roles_mapping.description',
- )}
- >
- <Button
- className="spacer-top"
- onClick={() => setShowMappingModal(true)}
- >
- {translate(
- 'settings.authentication.github.configuration.roles_mapping.button_label',
- )}
- </Button>
- </AuthenticationFormFieldWrapper>
- </>
- )}
- </>
- ) : (
- <p>
- <FormattedMessage
- id="settings.authentication.github.form.provisioning.disabled"
- defaultMessage={translate(
- 'settings.authentication.github.form.provisioning.disabled',
- )}
- values={{
- documentation: (
- // Documentation page not ready yet.
- <DocLink to="/instance-administration/authentication/github">
- {translate('documentation')}
- </DocLink>
- ),
- }}
- />
- </p>
+ {translate(
+ 'settings.authentication.github.configuration.roles_mapping.button_label',
)}
- </RadioCard>
+ </ButtonSecondary>
</div>
- ) : (
- <Alert className="big-spacer-top" variant="info">
- {translate('settings.authentication.github.enable_first')}
- </Alert>
- )}
- </fieldset>
- {enabled && (
- <div className="sw-flex sw-gap-2 sw-h-8 sw-items-center">
- <SubmitButton disabled={!hasGithubProvisioningConfigChange}>
- {translate('save')}
- </SubmitButton>
- <ResetButtonLink
- onClick={() => {
- setProvisioningType(undefined);
- resetJitSetting();
- }}
- disabled={!hasGithubProvisioningConfigChange}
- >
- {translate('cancel')}
- </ResetButtonLink>
- <Alert variant="warning" className="sw-mb-0">
- {hasGithubProvisioningConfigChange &&
- translate('settings.authentication.github.configuration.unsaved_changes')}
- </Alert>
+ <Note className="sw-mt-2">
+ {translate(
+ 'settings.authentication.github.configuration.roles_mapping.description',
+ )}
+ </Note>
</div>
+ </>
+ }
+ />
+ {showConfirmProvisioningModal && (
+ <ConfirmModal
+ onConfirm={() => changeProvisioning()}
+ header={translate(
+ 'settings.authentication.github.confirm',
+ newGithubProvisioningStatus ? 'auto' : 'jit',
)}
- {showConfirmProvisioningModal && (
- <ConfirmModal
- onConfirm={() => changeProvisioning()}
- header={translate(
- 'settings.authentication.github.confirm',
- newGithubProvisioningStatus ? 'auto' : 'jit',
- )}
- onClose={() => setShowConfirmProvisioningModal(false)}
- confirmButtonText={translate(
- 'settings.authentication.github.provisioning_change.confirm_changes',
- )}
- >
- {translate(
- 'settings.authentication.github.confirm',
- newGithubProvisioningStatus ? 'auto' : 'jit',
- 'description',
- )}
- </ConfirmModal>
+ onClose={() => setShowConfirmProvisioningModal(false)}
+ confirmButtonText={translate(
+ 'settings.authentication.github.provisioning_change.confirm_changes',
)}
- </form>
- {showMappingModal && (
- <GitHubMappingModal
- mapping={rolesMapping}
- setMapping={setRolesMapping}
- onClose={() => setShowMappingModal(false)}
- />
- )}
- </div>
+ >
+ {translate(
+ 'settings.authentication.github.confirm',
+ newGithubProvisioningStatus ? 'auto' : 'jit',
+ 'description',
+ )}
+ </ConfirmModal>
+ )}
+ {showMappingModal && (
+ <GitHubMappingModal
+ mapping={rolesMapping}
+ setMapping={setRolesMapping}
+ onClose={() => setShowMappingModal(false)}
+ />
+ )}
</>
)}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.
+ */
+import {
+ BasicSeparator,
+ ButtonPrimary,
+ ButtonSecondary,
+ FlagMessage,
+ RadioButton,
+ SubHeading,
+} from 'design-system';
+import React, { FormEvent, ReactElement } from 'react';
+import { translate } from '../../../../helpers/l10n';
+import { ProvisioningType } from '../../../../types/provisioning';
+
+interface Props {
+ provisioningType: ProvisioningType;
+ onChangeProvisioningType: (val: ProvisioningType) => void;
+ disabledConfigText: string;
+ jitTitle: string;
+ jitDescription: string | ReactElement;
+ jitSettings?: ReactElement;
+ autoTitle: string;
+ autoDescription: ReactElement;
+ synchronizationDetails?: ReactElement;
+ autoSettings?: ReactElement;
+ hasFeatureEnabled: boolean;
+ hasDifferentProvider: boolean;
+ autoFeatureDisabledText: string | ReactElement;
+ onSave: (e: FormEvent) => void;
+ onSyncNow?: () => void;
+ onCancel: () => void;
+ hasUnsavedChanges: boolean;
+ canSave?: boolean;
+ canSync?: boolean;
+ enabled: boolean;
+}
+
+export default function ProvisioningSection(props: Readonly<Props>) {
+ const {
+ provisioningType,
+ jitTitle,
+ jitDescription,
+ jitSettings,
+ autoTitle,
+ autoDescription,
+ autoSettings,
+ hasFeatureEnabled,
+ hasDifferentProvider,
+ autoFeatureDisabledText,
+ synchronizationDetails,
+ onChangeProvisioningType,
+ onSave,
+ onSyncNow,
+ onCancel,
+ hasUnsavedChanges,
+ enabled,
+ disabledConfigText,
+ canSave = true,
+ canSync,
+ } = props;
+ return (
+ <div className="sw-mb-2">
+ <form onSubmit={onSave}>
+ <SubHeading as="h5">{translate('settings.authentication.form.provisioning')}</SubHeading>
+ {enabled ? (
+ <>
+ <ul>
+ <li>
+ <RadioButton
+ id="jit"
+ checked={provisioningType === ProvisioningType.jit}
+ onCheck={onChangeProvisioningType}
+ className="sw-items-start"
+ value={ProvisioningType.jit}
+ >
+ <div>
+ <div className="sw-body-sm-highlight">{jitTitle}</div>
+
+ <div className="sw-mt-1">{jitDescription}</div>
+ </div>
+ </RadioButton>
+ {provisioningType === ProvisioningType.jit && jitSettings && (
+ <div className="sw-ml-16 sw-mt-6 sw-max-w-[435px]">{jitSettings}</div>
+ )}
+ <BasicSeparator className="sw-my-4" />
+ </li>
+ <li>
+ <RadioButton
+ id="github-auto"
+ className="sw-items-start"
+ checked={provisioningType === ProvisioningType.auto}
+ onCheck={onChangeProvisioningType}
+ value={ProvisioningType.auto}
+ disabled={!hasFeatureEnabled || hasDifferentProvider}
+ >
+ <div>
+ <div className="sw-body-sm-highlight">{autoTitle}</div>
+ <div className="sw-mt-1">
+ {hasFeatureEnabled ? (
+ <>
+ {hasDifferentProvider && (
+ <p className="sw-mb-2 sw-body-sm-highlight">
+ {translate('settings.authentication.form.other_provisioning_enabled')}
+ </p>
+ )}
+ {autoDescription}
+ </>
+ ) : (
+ autoFeatureDisabledText
+ )}
+ </div>
+ </div>
+ </RadioButton>
+ {provisioningType === ProvisioningType.auto && (
+ <div className="sw-ml-6 sw-mt-6">
+ {synchronizationDetails}
+ {onSyncNow && (
+ <div className="sw-mb-4 sw-mt-6">
+ <ButtonPrimary onClick={onSyncNow} disabled={!canSync}>
+ {translate('settings.authentication.github.synchronize_now')}
+ </ButtonPrimary>
+ </div>
+ )}
+ <div className="sw-ml-10 sw-mt-8 sw-max-w-[435px]">{autoSettings}</div>
+ </div>
+ )}
+ <BasicSeparator className="sw-my-4" />
+ </li>
+ </ul>
+ <div className="sw-flex sw-gap-2 sw-h-8 sw-items-center">
+ <ButtonPrimary type="submit" disabled={!hasUnsavedChanges || !canSave}>
+ {translate('save')}
+ </ButtonPrimary>
+ <ButtonSecondary onClick={onCancel} disabled={!hasUnsavedChanges}>
+ {translate('cancel')}
+ </ButtonSecondary>
+ <FlagMessage variant="warning" className="sw-mb-0">
+ {hasUnsavedChanges &&
+ translate('settings.authentication.github.configuration.unsaved_changes')}
+ </FlagMessage>
+ </div>
+ </>
+ ) : (
+ <FlagMessage className="sw-mt-4" variant="info">
+ {disabledConfigText}
+ </FlagMessage>
+ )}
+ </form>
+ </div>
+ );
+}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import React from 'react';
+import { ButtonSecondary, Spinner } from 'design-system';
+import React, { FormEvent } from 'react';
import { FormattedMessage } from 'react-intl';
-import DocLink from '../../../../components/common/DocLink';
-import Link from '../../../../components/common/Link';
+import DocumentationLink from '../../../../components/common/DocumentationLink';
import ConfirmModal from '../../../../components/controls/ConfirmModal';
-import RadioCard from '../../../../components/controls/RadioCard';
-import { Button, ResetButtonLink, SubmitButton } from '../../../../components/controls/buttons';
-import CheckIcon from '../../../../components/icons/CheckIcon';
-import DeleteIcon from '../../../../components/icons/DeleteIcon';
-import EditIcon from '../../../../components/icons/EditIcon';
-import { Alert } from '../../../../components/ui/Alert';
-import Spinner from '../../../../components/ui/Spinner';
import { translate } from '../../../../helpers/l10n';
import { useIdentityProviderQuery } from '../../../../queries/identity-provider/common';
import { useToggleScimMutation } from '../../../../queries/identity-provider/scim';
import { useSaveValueMutation } from '../../../../queries/settings';
+import { ProvisioningType } from '../../../../types/provisioning';
import { ExtendedSettingDefinition } from '../../../../types/settings';
import { Provider } from '../../../../types/types';
+import ConfigurationDetails from './ConfigurationDetails';
import ConfigurationForm from './ConfigurationForm';
+import ProvisioningSection from './ProvisioningSection';
+import TabHeader from './TabHeader';
import useSamlConfiguration, {
SAML_ENABLED_FIELD,
SAML_SCIM_DEPRECATED,
return (
<Spinner loading={isLoading}>
- <div className="authentication-configuration">
- <div className="spacer-bottom display-flex-space-between display-flex-center">
- <h4>{translate('settings.authentication.saml.configuration')}</h4>
+ <div>
+ <TabHeader
+ title={translate('settings.authentication.saml.configuration')}
+ showCreate={!hasConfiguration}
+ onCreate={handleCreateConfiguration}
+ />
- {!hasConfiguration && (
- <div>
- <Button onClick={handleCreateConfiguration}>
- {translate('settings.authentication.form.create')}
- </Button>
- </div>
- )}
- </div>
{!hasConfiguration && (
- <div className="big-padded text-center huge-spacer-bottom authentication-no-config">
- {translate('settings.authentication.saml.form.not_configured')}
- </div>
+ <div>{translate('settings.authentication.saml.form.not_configured')}</div>
)}
{hasConfiguration && (
<>
- <div className="spacer-bottom big-padded bordered display-flex-space-between">
- <div>
- <h5>{name}</h5>
- <p>{url}</p>
- <p className="big-spacer-top big-spacer-bottom">
- {samlEnabled ? (
- <span className="authentication-enabled spacer-left">
- <CheckIcon className="spacer-right" />
- {translate('settings.authentication.form.enabled')}
- </span>
- ) : (
- translate('settings.authentication.form.not_enabled')
- )}
- </p>
- <Button className="spacer-top" disabled={scimStatus} onClick={handleToggleEnable}>
- {samlEnabled
- ? translate('settings.authentication.form.disable')
- : translate('settings.authentication.form.enable')}
- </Button>
- </div>
- <div>
- <Link className="button spacer-right" target="_blank" to={CONFIG_TEST_PATH}>
+ <ConfigurationDetails
+ title={name?.toString() ?? ''}
+ url={url}
+ canDisable={!scimStatus}
+ enabled={samlEnabled}
+ isDeleting={isDeleting}
+ onEdit={handleCreateConfiguration}
+ onDelete={deleteConfiguration}
+ onToggle={handleToggleEnable}
+ extraActions={
+ <ButtonSecondary target="_blank" to={CONFIG_TEST_PATH}>
{translate('settings.authentication.saml.form.test')}
- </Link>
- <Button className="spacer-right" onClick={handleCreateConfiguration}>
- <EditIcon />
- {translate('settings.authentication.form.edit')}
- </Button>
- <Button
- className="button-red"
- disabled={samlEnabled || isDeleting}
- onClick={deleteConfiguration}
- >
- <DeleteIcon />
- {translate('settings.authentication.form.delete')}
- </Button>
- </div>
- </div>
- <div className="spacer-bottom big-padded bordered display-flex-space-between">
- <form
- onSubmit={(e) => {
- e.preventDefault();
- if (hasScimTypeChange) {
- setShowConfirmProvisioningModal(true);
- } else {
- handleSaveGroup();
- }
- }}
- >
- <fieldset className="display-flex-column big-spacer-bottom">
- <label className="h5">
- {translate('settings.authentication.form.provisioning')}
- </label>
- {samlEnabled ? (
- <div className="display-flex-column spacer-top">
- <RadioCard
- className="sw-min-h-0"
- label={translate('settings.authentication.saml.form.provisioning_at_login')}
- title={translate('settings.authentication.saml.form.provisioning_at_login')}
- selected={!(newScimStatus ?? scimStatus)}
- onClick={() => setNewScimStatus(false)}
- >
- <p>
- {translate('settings.authentication.saml.form.provisioning_at_login.sub')}
- </p>
- </RadioCard>
- <RadioCard
- className="spacer-top sw-min-h-0"
- label={translate(
- 'settings.authentication.saml.form.provisioning_with_scim',
- )}
- title={translate(
- 'settings.authentication.saml.form.provisioning_with_scim',
- )}
- selected={newScimStatus ?? scimStatus}
- onClick={() => setNewScimStatus(true)}
- disabled={!hasScim || hasDifferentProvider}
- >
- {!hasScim ? (
- <p>
- <FormattedMessage
- id="settings.authentication.saml.form.provisioning.disabled"
- values={{
- documentation: (
- <DocLink to="/instance-administration/authentication/saml/scim/overview">
- {translate('documentation')}
- </DocLink>
- ),
- }}
- />
- </p>
- ) : (
- <>
- {hasDifferentProvider && (
- <p className="spacer-bottom text-bold">
- {translate(
- 'settings.authentication.form.other_provisioning_enabled',
- )}
- </p>
- )}
- <p className="spacer-bottom ">
- {translate(
- 'settings.authentication.saml.form.provisioning_with_scim.sub',
- )}
- </p>
- <p className="spacer-bottom ">
- {translate(
- 'settings.authentication.saml.form.provisioning_with_scim.description',
- )}
- </p>
- <p>
- <FormattedMessage
- id="settings.authentication.saml.form.provisioning_with_scim.description.doc"
- defaultMessage={translate(
- 'settings.authentication.saml.form.provisioning_with_scim.description.doc',
- )}
- values={{
- documentation: (
- <DocLink to="/instance-administration/authentication/saml/scim/overview">
- {translate('documentation')}
- </DocLink>
- ),
- }}
- />
- </p>
- </>
- )}
- </RadioCard>
- </div>
- ) : (
- <Alert className="big-spacer-top" variant="info">
- {translate('settings.authentication.saml.enable_first')}
- </Alert>
- )}
- </fieldset>
- {samlEnabled && (
- <>
- <SubmitButton disabled={!hasScimConfigChange}>{translate('save')}</SubmitButton>
- <ResetButtonLink
- className="spacer-left"
- onClick={() => {
- setNewScimStatus(undefined);
- setNewGroupSetting();
- }}
- disabled={!hasScimConfigChange}
- >
- {translate('cancel')}
- </ResetButtonLink>
- </>
- )}
- {showConfirmProvisioningModal && (
- <ConfirmModal
- onConfirm={() => handleConfirmChangeProvisioning()}
- header={translate(
- 'settings.authentication.saml.confirm',
- newScimStatus ? 'scim' : 'jit',
- )}
- onClose={() => setShowConfirmProvisioningModal(false)}
- isDestructive={!newScimStatus}
- confirmButtonText={translate('yes')}
- >
+ </ButtonSecondary>
+ }
+ />
+ <ProvisioningSection
+ provisioningType={
+ newScimStatus ?? scimStatus ? ProvisioningType.auto : ProvisioningType.jit
+ }
+ onChangeProvisioningType={(val: ProvisioningType) =>
+ setNewScimStatus(val === ProvisioningType.auto)
+ }
+ disabledConfigText={translate('settings.authentication.saml.enable_first')}
+ enabled={samlEnabled}
+ hasUnsavedChanges={hasScimConfigChange}
+ onSave={(e: FormEvent) => {
+ e.preventDefault();
+ if (hasScimTypeChange) {
+ setShowConfirmProvisioningModal(true);
+ } else {
+ handleSaveGroup();
+ }
+ }}
+ onCancel={() => {
+ setNewScimStatus(undefined);
+ setNewGroupSetting();
+ }}
+ jitTitle={translate('settings.authentication.saml.form.provisioning_at_login')}
+ jitDescription={translate(
+ 'settings.authentication.saml.form.provisioning_at_login.sub',
+ )}
+ autoTitle={translate('settings.authentication.saml.form.provisioning_with_scim')}
+ hasDifferentProvider={hasDifferentProvider}
+ hasFeatureEnabled={hasScim}
+ autoFeatureDisabledText={
+ <FormattedMessage
+ id="settings.authentication.saml.form.provisioning.disabled"
+ values={{
+ documentation: (
+ <DocumentationLink to="/instance-administration/authentication/saml/scim/overview">
+ {translate('documentation')}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ }
+ autoDescription={
+ <>
+ <p className="sw-mb-2">
+ {translate('settings.authentication.saml.form.provisioning_with_scim.sub')}
+ </p>
+ <p className="sw-mb-2">
{translate(
- 'settings.authentication.saml.confirm',
- newScimStatus ? 'scim' : 'jit',
- 'description',
+ 'settings.authentication.saml.form.provisioning_with_scim.description',
)}
- </ConfirmModal>
+ </p>
+ <p>
+ <FormattedMessage
+ id="settings.authentication.saml.form.provisioning_with_scim.description.doc"
+ defaultMessage={translate(
+ 'settings.authentication.saml.form.provisioning_with_scim.description.doc',
+ )}
+ values={{
+ documentation: (
+ <DocumentationLink to="/instance-administration/authentication/saml/scim/overview">
+ {translate('documentation')}
+ </DocumentationLink>
+ ),
+ }}
+ />
+ </p>
+ </>
+ }
+ />
+ {showConfirmProvisioningModal && (
+ <ConfirmModal
+ onConfirm={() => handleConfirmChangeProvisioning()}
+ header={translate(
+ 'settings.authentication.saml.confirm',
+ newScimStatus ? 'scim' : 'jit',
+ )}
+ onClose={() => setShowConfirmProvisioningModal(false)}
+ isDestructive={!newScimStatus}
+ confirmButtonText={translate('yes')}
+ >
+ {translate(
+ 'settings.authentication.saml.confirm',
+ newScimStatus ? 'scim' : 'jit',
+ 'description',
)}
- </form>
- </div>
+ </ConfirmModal>
+ )}
</>
)}
{showEditModal && (
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.
+ */
+import { BasicSeparator, ButtonPrimary, SubHeading } from 'design-system';
+import React, { ReactElement } from 'react';
+import { translate } from '../../../../helpers/l10n';
+
+interface Props {
+ title: string;
+ showCreate: boolean;
+ onCreate: () => void;
+ configurationValidity?: ReactElement;
+}
+
+export default function TabHeader({
+ title,
+ showCreate,
+ onCreate,
+ configurationValidity,
+}: Readonly<Props>) {
+ return (
+ <>
+ <div className="sw-mb-4">
+ <SubHeading as="h4">{title}</SubHeading>
+ {showCreate && (
+ <ButtonPrimary className="sw-mt-2" onClick={onCreate}>
+ {translate('settings.authentication.form.create')}
+ </ButtonPrimary>
+ )}
+ </div>
+ {configurationValidity}
+ <BasicSeparator className="sw-my-6" />
+ </>
+ );
+}
}),
enableFirstMessage: ghContainer.byText('settings.authentication.github.enable_first'),
jitProvisioningButton: ghContainer.byRole('radio', {
- name: 'settings.authentication.form.provisioning_at_login',
+ name: /settings.authentication.form.provisioning_at_login/,
}),
githubProvisioningButton: ghContainer.byRole('radio', {
- name: 'settings.authentication.github.form.provisioning_with_github',
+ name: /settings.authentication.github.form.provisioning_with_github/,
}),
- githubProvisioningPending: ghContainer.byText(/synchronization_pending/),
- githubProvisioningInProgress: ghContainer.byText(/synchronization_in_progress/),
+ githubProvisioningPending: ghContainer
+ .byRole('list')
+ .byRole('status')
+ .byText(/synchronization_pending/),
+ githubProvisioningInProgress: ghContainer
+ .byRole('list')
+ .byRole('status')
+ .byText(/synchronization_in_progress/),
githubProvisioningSuccess: ghContainer.byText(/synchronization_successful/),
githubProvisioningAlert: ghContainer.byText(/synchronization_failed/),
configurationValidityLoading: ghContainer.byRole('status', {
renderAuthentication([Feature.GithubProvisioning]);
await ui.enableProvisioning(user);
expect(ui.githubProvisioningAlert.get()).toBeInTheDocument();
- expect(ui.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
+ expect(ghContainer.get()).toHaveTextContent("T'es mauvais Jacques");
expect(ui.githubProvisioningSuccess.query()).not.toBeInTheDocument();
});
renderAuthentication([Feature.GithubProvisioning]);
await ui.enableProvisioning(user);
expect(ui.githubProvisioningAlert.get()).toBeInTheDocument();
- expect(ui.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
+ expect(ghContainer.get()).toHaveTextContent("T'es mauvais Jacques");
expect(ui.githubProvisioningSuccess.query()).not.toBeInTheDocument();
expect(ui.githubProvisioningInProgress.get()).toBeInTheDocument();
});
}),
saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
jitProvisioningRadioButton: glContainer.byRole('radio', {
- name: 'settings.authentication.gitlab.provisioning_at_login',
+ name: /settings.authentication.gitlab.provisioning_at_login/,
}),
autoProvisioningRadioButton: glContainer.byRole('radio', {
- name: 'settings.authentication.gitlab.form.provisioning_with_gitlab',
+ name: /settings.authentication.gitlab.form.provisioning_with_gitlab/,
}),
jitAllowUsersToSignUpToggle: byRole('switch', { name: 'property.allowUsersToSignUp.name' }),
autoProvisioningToken: byRole('textbox', {
}),
syncSummary: glContainer.byText(/Test summary/),
syncWarning: glContainer.byText(/Warning/),
- gitlabProvisioningPending: glContainer.byText(/synchronization_pending/),
- gitlabProvisioningInProgress: glContainer.byText(/synchronization_in_progress/),
+ gitlabProvisioningPending: glContainer
+ .byRole('list')
+ .byRole('status')
+ .byText(/synchronization_pending/),
+ gitlabProvisioningInProgress: glContainer
+ .byRole('list')
+ .byRole('status')
+ .byText(/synchronization_in_progress/),
gitlabProvisioningSuccess: glContainer.byText(/synchronization_successful/),
gitlabProvisioningAlert: glContainer.byText(/synchronization_failed/),
- gitlabConfigurationStatus: glContainer.byRole('status'),
+ gitlabConfigurationStatus: glContainer.byRole('status', {
+ name: /settings.authentication.gitlab.configuration/,
+ }),
testConfiguration: glContainer.byRole('button', {
name: 'settings.authentication.configuration.test',
}),
});
renderAuthentication([Feature.GitlabProvisioning]);
expect(await ui.gitlabProvisioningAlert.find()).toBeInTheDocument();
- expect(ui.autoProvisioningRadioButton.get()).toHaveTextContent("T'es mauvais Jacques");
+ expect(glContainer.get()).toHaveTextContent("T'es mauvais Jacques");
expect(ui.gitlabProvisioningSuccess.query()).not.toBeInTheDocument();
});
});
renderAuthentication([Feature.GitlabProvisioning]);
expect(await ui.gitlabProvisioningAlert.find()).toBeInTheDocument();
- expect(ui.autoProvisioningRadioButton.get()).toHaveTextContent("T'es mauvais Jacques");
+ expect(glContainer.get()).toHaveTextContent("T'es mauvais Jacques");
expect(ui.gitlabProvisioningSuccess.query()).not.toBeInTheDocument();
expect(ui.gitlabProvisioningInProgress.get()).toBeInTheDocument();
});
const user = userEvent.setup();
renderAuthentication([Feature.GitlabProvisioning]);
- expect((await ui.gitlabConfigurationStatus.findAll())[1]).toHaveTextContent(
+ expect(await ui.gitlabConfigurationStatus.find()).toHaveTextContent(
'settings.authentication.gitlab.configuration.valid.AUTO_PROVISIONING',
);
await user.click(ui.jitProvisioningRadioButton.get());
await user.click(ui.saveProvisioning.get());
await user.click(ui.confirmProvisioningChange.get());
- expect(ui.gitlabConfigurationStatus.getAll()[1]).toHaveTextContent(
+ expect(ui.gitlabConfigurationStatus.get()).toHaveTextContent(
'settings.authentication.gitlab.configuration.valid.JIT',
);
handler.setGitlabConfigurations([
mockGitlabConfiguration({ ...handler.gitlabConfigurations[0], errorMessage: 'ERROR' }),
]);
await user.click(ui.testConfiguration.get());
- expect(ui.gitlabConfigurationStatus.getAll()[1]).toHaveTextContent('ERROR');
+ expect(glContainer.get()).toHaveTextContent('ERROR');
await user.click(ui.disableConfigButton.get());
expect(ui.gitlabConfigurationStatus.query()).not.toBeInTheDocument();
});
}),
enableFirstMessage: byText('settings.authentication.saml.enable_first'),
jitProvisioningButton: byRole('radio', {
- name: 'settings.authentication.saml.form.provisioning_at_login',
+ name: /settings.authentication.saml.form.provisioning_at_login/,
}),
scimProvisioningButton: byRole('radio', {
- name: 'settings.authentication.saml.form.provisioning_with_scim',
+ name: /settings.authentication.saml.form.provisioning_with_scim/,
}),
fillForm: async (user: UserEvent) => {
await user.clear(ui.providerName.get());
expect(screen.getAllByRole('tab')).toHaveLength(4);
- expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'true');
+ expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-current', 'true');
await user.click(screen.getByRole('tab', { name: 'github GitHub' }));
- expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'false');
+ expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-current', 'false');
expect(screen.getByRole('tab', { name: 'github GitHub' })).toHaveAttribute(
- 'aria-selected',
+ 'aria-current',
'true',
);
});
padding: 16px;
overflow-wrap: break-word;
}
-
-.authentication-enabled {
- color: var(--success500);
-}
-
-.authentication-no-config {
- background-color: var(--neutral50);
- color: var(--blacka60);
-}
-
-.authentication-configuration .radio-card {
- width: 100%;
- min-height: 250px;
- background-color: var(--neutral50);
- border: 1px solid var(--neutral200);
-}
-
-.authentication-configuration .radio-card.selected {
- background-color: var(--info50);
- border: 1px solid var(--info500);
-}
-
-.authentication-configuration .radio-card:hover:not(.selected) {
- border: 1px solid var(--info500);
-}
-
-.authentication-configuration fieldset > div {
- justify-content: space-between;
-}
-
-.authentication-configuration .radio-card-body {
- justify-content: flex-start;
-}
-
-.authentication-configuration .settings-definition-left {
- width: 50%;
-}
-
-.authentication-configuration .settings-definition-right {
- align-items: end;
- width: 20%;
-}
settings.authentication.form.delete=Delete
settings.authentication.form.delete.tooltip=You can only delete a configuration if it is disabled.
settings.authentication.form.loading=Loading configuration
-settings.authentication.form.enabled=Enabled
-settings.authentication.form.not_enabled=This configuration is disabled
settings.authentication.form.enable=Enable configuration
settings.authentication.form.disable=Disable configuration
settings.authentication.form.disable.tooltip=You can not disable this configuration while using Automatic Provisioning. You must be in Just-in-Time Provisioning mode to disable this configuration.
settings.authentication.github.form.provisioning_with_github=Automatic user, group, and permission provisioning
settings.authentication.github.form.provisioning_with_github_short.autoProvisioning=Automatic provisioning
settings.authentication.github.form.provisioning_with_github_short.jit=Just-in-Time provisioning
-settings.authentication.github.form.provisioning_with_github.description=Users, groups and permissions are automatically provisioned from your GitHub organizations. Once activated, users and groups can only be created and modified from your GitHub organizations/teams. Existing local users will be kept and can only be deactivated.
+settings.authentication.github.form.provisioning_with_github.description=Users, groups and permissions are automatically provisioned from your GitHub organizations. Once activated, users and groups can only be created and modified from your GitHub organizations/teams. Existing local users will be kept and can only be deactivated. {documentation}
settings.authentication.github.form.description.doc=For more details, see {documentation}.
-settings.authentication.github.form.provisioning_at_login.description=Users and groups are synchronized only when users log in to SonarQube.
+settings.authentication.github.form.provisioning_at_login.description=Users and groups are synchronized only when users log in to SonarQube. {documentation}
settings.authentication.github.form.provisioning.disabled=Your current edition does not support provisioning with GitHub. See the {documentation} for more information.
settings.authentication.github.synchronize_now=Synchronize now
settings.authentication.github.synchronization_in_progress=Synchronization is in progress.
settings.authentication.gitlab.form.allowUsersToSignUp.description=Allow new users to authenticate. When set to disabled, only existing users will be able to authenticate to the server.
settings.authentication.gitlab.form.provisioningToken.name=Provisioning token
settings.authentication.gitlab.form.provisioningToken.description=Token used for user provisioning. You can either use a group or a personal access token, as long as it has visibility on the groups that need to be imported.
+settings.authentication.gitlab.applicationId.name=App ID: {0}
settings.authentication.gitlab.enable_first=Enable your GitLab configuration for more provisioning options.
settings.authentication.gitlab.provisioning_at_login=Just-in-Time user provisioning (default)
-settings.authentication.gitlab.provisioning_at_login.description=Users are synchronized only when users log in to SonarQube.
+settings.authentication.gitlab.provisioning_at_login.description=Users are synchronized only when users log in to SonarQube. {documentation}
settings.authentication.gitlab.description.JIT.learn_more=Learn more about Just-in-Time provisioning with GitLab
settings.authentication.gitlab.description.AUTO_PROVISIONING.learn_more=Learn more about automatic provisioning with GitLab
settings.authentication.gitlab.confirm.AUTO_PROVISIONING=Switch to automatic provisioning
settings.authentication.gitlab.confirm.JIT.description=Switching to Just-in-Time provisioning removes the automatic synchronization of users and groups. Users are provisioned and updated only at user login. Are you sure?
settings.authentication.gitlab.provisioning_change.confirm_changes=Confirm Changes
settings.authentication.gitlab.form.provisioning_with_gitlab=Automatic user and group provisioning
-settings.authentication.gitlab.form.provisioning_with_gitlab.description=Users and groups are automatically provisioned from GitLab. Once activated, users and groups can only be created and modified from GitLab. Existing local users will be kept and can only be deactivated.
+settings.authentication.gitlab.form.provisioning_with_gitlab.description=Users and groups are automatically provisioned from GitLab. Once activated, users and groups can only be created and modified from GitLab. Existing local users will be kept and can only be deactivated. {documentation}
settings.authentication.gitlab.form.provisioning.disabled=Your current edition does not support provisioning with GitLab. See the {documentation} for more information.
settings.authentication.gitlab.configuration.unsaved_changes=You have unsaved changes.
settings.authentication.gitlab.configuration.valid.JIT=Configuration is valid for Just-in-Time provisioning.