From: Viktor Vorona Date: Fri, 26 Jan 2024 10:11:32 +0000 (+0100) Subject: SONAR-21480 migrate authentication tabs to MIUI X-Git-Tag: 10.4.0.87286~83 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=4edb157cedb8327b0417bd7a9e472ff145f79445;p=sonarqube.git SONAR-21480 migrate authentication tabs to MIUI --- diff --git a/server/sonar-web/design-system/src/components/FlagMessage.tsx b/server/sonar-web/design-system/src/components/FlagMessage.tsx index 767af9b1631..47fb9b9fa58 100644 --- a/server/sonar-web/design-system/src/components/FlagMessage.tsx +++ b/server/sonar-web/design-system/src/components/FlagMessage.tsx @@ -71,10 +71,12 @@ export function FlagMessage(props: Props & React.HTMLAttributes) className={classNames('alert', className)} {...domProps} > -
-
{variantInfo.icon}
-
{props.children}
-
+ {props.children && ( +
+
{variantInfo.icon}
+
{props.children}
+
+ )} ); } @@ -117,6 +119,10 @@ export const StyledFlag = styled.div<{ border: ${({ borderColor }) => themeBorder('default', borderColor)}; background-color: ${themeColor('flagMessageBackground')}; + :empty { + display: none; + } + & > .flag-inner { ${tw`sw-flex sw-items-stretch`} ${tw`sw-box-border`} diff --git a/server/sonar-web/src/main/js/app/components/AlmSynchronisationWarning.tsx b/server/sonar-web/src/main/js/app/components/AlmSynchronisationWarning.tsx index 238236156ce..9f6259e4a32 100644 --- a/server/sonar-web/src/main/js/app/components/AlmSynchronisationWarning.tsx +++ b/server/sonar-web/src/main/js/app/components/AlmSynchronisationWarning.tsx @@ -19,10 +19,9 @@ */ 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'; @@ -82,53 +81,59 @@ function LastSyncAlert({ info, short }: Readonly) { ) : ( - - {translate('settings.authentication.github.synchronization_details_link')} - - ), - }} - /> +
+ + {translate('settings.authentication.github.synchronization_details_link')} + + ), + }} + /> +
); } return ( <> - - {status === TaskStatuses.Success ? ( - <> - {translateWithParameters( - 'settings.authentication.github.synchronization_successful', - formattedDate, - )} -
- {summary ?? ''} - - ) : ( - -
+
+ {status === TaskStatuses.Success ? ( + <> {translateWithParameters( - 'settings.authentication.github.synchronization_failed', + 'settings.authentication.github.synchronization_successful', formattedDate, )} -
-
- {errorMessage ?? ''} - - )} - - +
+ {summary ?? ''} + + ) : ( + +
+ {translateWithParameters( + 'settings.authentication.github.synchronization_failed', + formattedDate, + )} +
+
+ {errorMessage ?? ''} +
+ )} +
+ + {warningMessage} -
+ ); } @@ -137,28 +142,21 @@ export default function AlmSynchronisationWarning({ short, data, }: Readonly) { + const loadingLabel = + data.nextSync && + translate( + data.nextSync.status === TaskStatuses.Pending + ? 'settings.authentication.github.synchronization_pending' + : 'settings.authentication.github.synchronization_in_progress', + ); return ( <> - - {!short && - data?.nextSync && - translate( - data.nextSync.status === TaskStatuses.Pending - ? 'settings.authentication.github.synchronization_pending' - : 'settings.authentication.github.synchronization_in_progress', - )} - + {!short && ( +
+ +
{data.nextSync && loadingLabel}
+
+ )} diff --git a/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.tsx b/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.tsx index 71c8c4e3bd4..92d95141b5b 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.tsx @@ -34,6 +34,7 @@ interface Props { definitions: ExtendedSettingDefinition[]; subCategory?: string; displaySubCategoryTitle?: boolean; + noPadding?: boolean; } interface State { @@ -87,7 +88,7 @@ export default class CategoryDefinitionsList extends React.PureComponent ); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx index 14abcc7a811..c5e2614aecf 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx @@ -35,6 +35,7 @@ export interface SubCategoryDefinitionsListProps { settings: Array; subCategory?: string; displaySubCategoryTitle?: boolean; + noPadding?: boolean; } class SubCategoryDefinitionsList extends React.PureComponent { @@ -65,7 +66,13 @@ class SubCategoryDefinitionsList extends React.PureComponent setting.definition.subCategory); const subCategories = Object.keys(bySubCategory).map((key) => ({ key, @@ -81,7 +88,7 @@ class SubCategoryDefinitionsList extends React.PureComponent {filteredSubCategories.map((subCategory, index) => ( -
  • +
  • {displaySubCategoryTitle && ( {renderDevOpsIcon(AlmKeys.GitHub)} @@ -94,7 +89,7 @@ export function Authentication(props: Props & WithAvailableFeaturesProps) { ), }, { - key: AlmKeys.BitbucketServer, + value: AlmKeys.BitbucketServer, label: ( <> {renderDevOpsIcon(AlmKeys.BitbucketServer)} @@ -103,7 +98,7 @@ export function Authentication(props: Props & WithAvailableFeaturesProps) { ), }, { - key: AlmKeys.GitLab, + value: AlmKeys.GitLab, label: ( <> {renderDevOpsIcon(AlmKeys.GitLab)} @@ -123,104 +118,92 @@ export function Authentication(props: Props & WithAvailableFeaturesProps) { return ( <> -
    -

    {translate('settings.authentication.title')}

    -
    + {translate('settings.authentication.title')} {props.hasFeature(Feature.LoginMessage) && ( - - - {translate('settings.authentication.custom_message_information.link')} - - ), - }} - /> - + +
    + + {translate('settings.authentication.custom_message_information.link')} + + ), + }} + /> +
    +
    )} -
    +

    {translate('settings.authentication.description')}

    - { + { 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 */} - - {({ top }) => ( - <> - {tabs.map((tab) => ( -
    - {currentTab === tab.key && ( -
    - {tab.key === SAML && } - - {tab.key === AlmKeys.GitHub && ( - ( +
    + {currentTab === tab.value && ( +
    + {tab.value === SAML && } + + {tab.value === AlmKeys.GitHub && ( + + )} + + {tab.value === AlmKeys.GitLab && } + + {tab.value === AlmKeys.BitbucketServer && ( + <> + +
    + + {translate('settings.authentication.help.link')} + + ), + }} /> - )} - - {tab.key === AlmKeys.GitLab && } - - {tab.key === AlmKeys.BitbucketServer && ( - <> - - - {translate('settings.authentication.help.link')} - - ), - }} - /> - - - - )} -
    - )} -
    - ))} - - )} - +
    + + + + )} +
    + )} +
    + ))} ); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/ConfigurationDetails.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/ConfigurationDetails.tsx new file mode 100644 index 00000000000..7d8308754e0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/ConfigurationDetails.tsx @@ -0,0 +1,84 @@ +/* + * 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) { + const { title, url, canDisable, onEdit, onDelete, onToggle, extraActions, isDeleting, enabled } = + props; + + return ( +
    +
    + + {title} + +

    {url}

    + + {enabled ? ( + + {translate('settings.authentication.form.disable')} + + ) : ( + + {translate('settings.authentication.form.enable')} + + )} + +
    +
    + {extraActions} + + {translate('settings.authentication.form.edit')} + + + + {translate('settings.authentication.form.delete')} + + +
    +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/GitLabAuthenticationTab.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/GitLabAuthenticationTab.tsx index 30c5db1c14d..a56cca16fce 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/GitLabAuthenticationTab.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/GitLabAuthenticationTab.tsx @@ -17,21 +17,15 @@ * 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, @@ -46,8 +40,11 @@ import { DefinitionV2, SettingType } from '../../../../types/settings'; 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']; @@ -187,254 +184,154 @@ export default function GitLabAuthenticationTab() { return ( -
    -
    -

    {translate('settings.authentication.gitlab.configuration')}

    - {!configuration && ( -
    - -
    - )} -
    - {!isLoadingList && configuration?.enabled && ( - - )} +
    + setOpenForm(true)} + configurationValidity={ + <> + {!isLoadingList && configuration?.enabled && ( + + )} + + } + /> {!configuration && ( -
    - {translate('settings.authentication.gitlab.form.not_configured')} -
    +
    {translate('settings.authentication.gitlab.form.not_configured')}
    )} {configuration && ( -
    -
    -

    {configuration.url}

    - - - -
    -
    - - - - -
    -
    - )} - {configuration && ( -
    -
    -
    - - - {configuration.enabled ? ( -
    - -

    - -

    -

    - - {translate( - `settings.authentication.gitlab.description.${ProvisioningType.jit}.learn_more`, - )} - -

    - {provisioningType === ProvisioningType.jit && - allowUsersToSignUpDefinition !== undefined && ( - - setChangesWithCheck({ - ...changes, - allowUsersToSignUp: value as boolean, - }) - } - isNotSet={configuration.provisioningType !== ProvisioningType.auto} - /> - )} -
    - - {hasGitlabProvisioningFeature ? ( - <> - {hasDifferentProvider && ( -

    - {translate('settings.authentication.form.other_provisioning_enabled')} -

    - )} -

    - {translate( - 'settings.authentication.gitlab.form.provisioning_with_gitlab.description', - )} -

    -

    - - {translate( - `settings.authentication.gitlab.description.${ProvisioningType.auto}.learn_more`, - )} - -

    - - {configuration.provisioningType === ProvisioningType.auto && ( - - )} - - {provisioningType === ProvisioningType.auto && ( - <> -
    - -
    -
    - - setChangesWithCheck({ - ...changes, - provisioningToken: value as string, - }) - } - isNotSet={!configuration.isProvisioningTokenSet} - /> - - )} - - ) : ( -

    - - {translate('documentation')} - - ), - }} - /> -

    - )} -
    -
    - ) : ( - - {translate('settings.authentication.gitlab.enable_first')} - - )} -
    - {configuration.enabled && ( -
    - {translate('save')} - { - setChanges(undefined); - setTokenKey(tokenKey + 1); - }} - disabled={false} - > - {translate('cancel')} - - - {canSave() && - translate('settings.authentication.gitlab.configuration.unsaved_changes')} - - -
    + <> + 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} + /> + + 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={ + + {translate(`learn_more`)} + + ), + }} + /> + } + jitSettings={ + + 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={ + - )} - -
    + values={{ + documentation: ( + + {translate('documentation')} + + ), + }} + /> + } + autoDescription={ + + {translate(`learn_more`)} + + ), + }} + /> + } + onSyncNow={synchronizeNow} + canSync={canSyncNow} + synchronizationDetails={} + autoSettings={ + + setChangesWithCheck({ + ...changes, + provisioningToken: value as string, + }) + } + isNotSet={!configuration.isProvisioningTokenSet} + /> + } + /> + )}
    + {showConfirmProvisioningModal && provisioningType && ( + setShowConfirmProvisioningModal(false)} + confirmButtonText={translate( + 'settings.authentication.gitlab.provisioning_change.confirm_changes', + )} + > + {translate('settings.authentication.gitlab.confirm', provisioningType, 'description')} + + )} {openForm && ( setOpenForm(false)} /> )} diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/GithubAuthenticationTab.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/GithubAuthenticationTab.tsx index d66da0f2a4c..287ef88a7e6 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/GithubAuthenticationTab.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/GithubAuthenticationTab.tsx @@ -17,18 +17,12 @@ * 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 { @@ -36,15 +30,18 @@ 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, @@ -112,325 +109,220 @@ export default function GithubAuthenticationTab(props: GithubAuthenticationProps } }; + const handleProvisioningTypeChange = (type: ProvisioningType) => { + setProvisioningType(type === ProvisioningType.auto); + }; + return ( -
    -
    -

    {translate('settings.authentication.github.configuration')}

    - - {!hasConfiguration && ( -
    - -
    - )} -
    - {enabled && !hasLegacyConfiguration && ( - - )} +
    + + {enabled && !hasLegacyConfiguration && ( + + )} + + } + /> {!hasConfiguration && !hasLegacyConfiguration && ( -
    - {translate('settings.authentication.github.form.not_configured')} -
    +
    {translate('settings.authentication.github.form.not_configured')}
    )} {!hasConfiguration && hasLegacyConfiguration && ( -
    - + +
    + {translate('settings.authentication.github.form.legacy_configured.link')} - + ), }} /> - -
    +
    + )} {hasConfiguration && ( <> -
    -
    -
    {translateWithParameters('settings.authentication.github.appid_x', appId)}
    -

    {url}

    - - - -
    -
    - - - - -
    -
    -
    -
    -
    - + - {enabled ? ( -
    - setProvisioningType(false)} + { + setProvisioningType(undefined); + resetJitSetting(); + }} + jitTitle={translate('settings.authentication.form.provisioning_at_login')} + jitDescription={ + -

    - -

    -

    - - {translate('documentation')} - - ), - }} - /> -

    - - {!(newGithubProvisioningStatus ?? githubProvisioningStatus) && ( - <> -
    - {Object.values(values).map((val) => { - if (!GITHUB_JIT_FIELDS.includes(val.key)) { - return null; - } - return ( -
    - -
    - ); - })} - - )} -
    - + ), + }} + /> + } + jitSettings={ + <> + {Object.values(values).map((val) => { + if (!GITHUB_JIT_FIELDS.includes(val.key)) { + return null; + } + return ( + + ); + })} + + } + autoTitle={translate('settings.authentication.github.form.provisioning_with_github')} + hasDifferentProvider={hasDifferentProvider} + hasFeatureEnabled={hasGithubProvisioning} + autoFeatureDisabledText={ + + {translate('documentation')} + + ), + }} + /> + } + autoDescription={ + + {translate('learn_more')} + + ), + }} + /> + } + synchronizationDetails={} + onSyncNow={synchronizeNow} + canSync={canSyncNow} + autoSettings={ + <> + {Object.values(values).map((val) => { + if (!GITHUB_PROVISIONING_FIELDS.includes(val.key)) { + return null; + } + return ( +
    + +
    + ); + })} +
    +
    + + {translate( + 'settings.authentication.github.configuration.roles_mapping.title', )} - selected={newGithubProvisioningStatus ?? githubProvisioningStatus} - onClick={() => setProvisioningType(true)} - disabled={!hasGithubProvisioning || hasDifferentProvider} + + setShowMappingModal(true)} > - {hasGithubProvisioning ? ( - <> - {hasDifferentProvider && ( -

    - {translate( - 'settings.authentication.form.other_provisioning_enabled', - )} -

    - )} -

    - {translate( - 'settings.authentication.github.form.provisioning_with_github.description', - )} -

    -

    - - {translate('documentation')} - - ), - }} - /> -

    - - {githubProvisioningStatus && } - {(newGithubProvisioningStatus ?? githubProvisioningStatus) && ( - <> -
    - -
    -
    - {Object.values(values).map((val) => { - if (!GITHUB_PROVISIONING_FIELDS.includes(val.key)) { - return null; - } - return ( -
    - -
    - ); - })} - - - - - )} - - ) : ( -

    - - {translate('documentation')} - - ), - }} - /> -

    + {translate( + 'settings.authentication.github.configuration.roles_mapping.button_label', )} - +
    - ) : ( - - {translate('settings.authentication.github.enable_first')} - - )} -
    - {enabled && ( -
    - - {translate('save')} - - { - setProvisioningType(undefined); - resetJitSetting(); - }} - disabled={!hasGithubProvisioningConfigChange} - > - {translate('cancel')} - - - {hasGithubProvisioningConfigChange && - translate('settings.authentication.github.configuration.unsaved_changes')} - + + {translate( + 'settings.authentication.github.configuration.roles_mapping.description', + )} +
    + + } + /> + {showConfirmProvisioningModal && ( + changeProvisioning()} + header={translate( + 'settings.authentication.github.confirm', + newGithubProvisioningStatus ? 'auto' : 'jit', )} - {showConfirmProvisioningModal && ( - 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', - )} - + onClose={() => setShowConfirmProvisioningModal(false)} + confirmButtonText={translate( + 'settings.authentication.github.provisioning_change.confirm_changes', )} - - {showMappingModal && ( - setShowMappingModal(false)} - /> - )} -
    + > + {translate( + 'settings.authentication.github.confirm', + newGithubProvisioningStatus ? 'auto' : 'jit', + 'description', + )} + + )} + {showMappingModal && ( + setShowMappingModal(false)} + /> + )} )} diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/ProvisioningSection.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/ProvisioningSection.tsx new file mode 100644 index 00000000000..014ebebcd4f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/ProvisioningSection.tsx @@ -0,0 +1,168 @@ +/* + * 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) { + const { + provisioningType, + jitTitle, + jitDescription, + jitSettings, + autoTitle, + autoDescription, + autoSettings, + hasFeatureEnabled, + hasDifferentProvider, + autoFeatureDisabledText, + synchronizationDetails, + onChangeProvisioningType, + onSave, + onSyncNow, + onCancel, + hasUnsavedChanges, + enabled, + disabledConfigText, + canSave = true, + canSync, + } = props; + return ( +
    +
    + {translate('settings.authentication.form.provisioning')} + {enabled ? ( + <> +
      +
    • + +
      +
      {jitTitle}
      + +
      {jitDescription}
      +
      +
      + {provisioningType === ProvisioningType.jit && jitSettings && ( +
      {jitSettings}
      + )} + +
    • +
    • + +
      +
      {autoTitle}
      +
      + {hasFeatureEnabled ? ( + <> + {hasDifferentProvider && ( +

      + {translate('settings.authentication.form.other_provisioning_enabled')} +

      + )} + {autoDescription} + + ) : ( + autoFeatureDisabledText + )} +
      +
      +
      + {provisioningType === ProvisioningType.auto && ( +
      + {synchronizationDetails} + {onSyncNow && ( +
      + + {translate('settings.authentication.github.synchronize_now')} + +
      + )} +
      {autoSettings}
      +
      + )} + +
    • +
    +
    + + {translate('save')} + + + {translate('cancel')} + + + {hasUnsavedChanges && + translate('settings.authentication.github.configuration.unsaved_changes')} + +
    + + ) : ( + + {disabledConfigText} + + )} +
    +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlAuthenticationTab.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlAuthenticationTab.tsx index 66a7bef88c8..b253dc80787 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlAuthenticationTab.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlAuthenticationTab.tsx @@ -17,25 +17,22 @@ * 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, @@ -108,196 +105,121 @@ export default function SamlAuthenticationTab(props: SamlAuthenticationProps) { return ( -
    -
    -

    {translate('settings.authentication.saml.configuration')}

    +
    + - {!hasConfiguration && ( -
    - -
    - )} -
    {!hasConfiguration && ( -
    - {translate('settings.authentication.saml.form.not_configured')} -
    +
    {translate('settings.authentication.saml.form.not_configured')}
    )} {hasConfiguration && ( <> -
    -
    -
    {name}
    -

    {url}

    -

    - {samlEnabled ? ( - - - {translate('settings.authentication.form.enabled')} - - ) : ( - translate('settings.authentication.form.not_enabled') - )} -

    - -
    -
    - + {translate('settings.authentication.saml.form.test')} - - - -
    -
    -
    -
    { - e.preventDefault(); - if (hasScimTypeChange) { - setShowConfirmProvisioningModal(true); - } else { - handleSaveGroup(); - } - }} - > -
    - - {samlEnabled ? ( -
    - setNewScimStatus(false)} - > -

    - {translate('settings.authentication.saml.form.provisioning_at_login.sub')} -

    -
    - setNewScimStatus(true)} - disabled={!hasScim || hasDifferentProvider} - > - {!hasScim ? ( -

    - - {translate('documentation')} - - ), - }} - /> -

    - ) : ( - <> - {hasDifferentProvider && ( -

    - {translate( - 'settings.authentication.form.other_provisioning_enabled', - )} -

    - )} -

    - {translate( - 'settings.authentication.saml.form.provisioning_with_scim.sub', - )} -

    -

    - {translate( - 'settings.authentication.saml.form.provisioning_with_scim.description', - )} -

    -

    - - {translate('documentation')} - - ), - }} - /> -

    - - )} -
    -
    - ) : ( - - {translate('settings.authentication.saml.enable_first')} - - )} -
    - {samlEnabled && ( - <> - {translate('save')} - { - setNewScimStatus(undefined); - setNewGroupSetting(); - }} - disabled={!hasScimConfigChange} - > - {translate('cancel')} - - - )} - {showConfirmProvisioningModal && ( - handleConfirmChangeProvisioning()} - header={translate( - 'settings.authentication.saml.confirm', - newScimStatus ? 'scim' : 'jit', - )} - onClose={() => setShowConfirmProvisioningModal(false)} - isDestructive={!newScimStatus} - confirmButtonText={translate('yes')} - > + + } + /> + + 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={ + + {translate('documentation')} + + ), + }} + /> + } + autoDescription={ + <> +

    + {translate('settings.authentication.saml.form.provisioning_with_scim.sub')} +

    +

    {translate( - 'settings.authentication.saml.confirm', - newScimStatus ? 'scim' : 'jit', - 'description', + 'settings.authentication.saml.form.provisioning_with_scim.description', )} - +

    +

    + + {translate('documentation')} + + ), + }} + /> +

    + + } + /> + {showConfirmProvisioningModal && ( + 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', )} - -
    + + )} )} {showEditModal && ( diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/TabHeader.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/TabHeader.tsx new file mode 100644 index 00000000000..5b9daefc18b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/TabHeader.tsx @@ -0,0 +1,51 @@ +/* + * 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) { + return ( + <> +
    + {title} + {showCreate && ( + + {translate('settings.authentication.form.create')} + + )} +
    + {configurationValidity} + + + ); +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-Github-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-Github-it.tsx index 121c39908a7..759a8d3c864 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-Github-it.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-Github-it.tsx @@ -138,13 +138,19 @@ const ui = { }), 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', { @@ -369,7 +375,7 @@ describe('Github tab', () => { 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(); }); @@ -386,7 +392,7 @@ describe('Github tab', () => { 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(); }); diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-Gitlab-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-Gitlab-it.tsx index 432e781aa0e..d9cfe1f9e8f 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-Gitlab-it.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-Gitlab-it.tsx @@ -90,10 +90,10 @@ const ui = { }), 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', { @@ -122,11 +122,19 @@ const ui = { }), 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', }), @@ -424,7 +432,7 @@ describe('Gitlab Provisioning', () => { }); 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(); }); @@ -442,7 +450,7 @@ describe('Gitlab Provisioning', () => { }); 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(); }); @@ -464,20 +472,20 @@ describe('Gitlab Provisioning', () => { 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(); }); diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-Scim-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-Scim-it.tsx index 48a34862af8..d2b8376f266 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-Scim-it.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-Scim-it.tsx @@ -96,10 +96,10 @@ const ui = { }), 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()); diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx index abcec5b1ded..bf2345a6c0e 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx @@ -66,13 +66,13 @@ it('should render tabs and allow navigation', async () => { 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', ); }); diff --git a/server/sonar-web/src/main/js/apps/settings/styles.css b/server/sonar-web/src/main/js/apps/settings/styles.css index 0ad2ea18013..2eaca9065a9 100644 --- a/server/sonar-web/src/main/js/apps/settings/styles.css +++ b/server/sonar-web/src/main/js/apps/settings/styles.css @@ -239,45 +239,3 @@ 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%; -} diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 3fdcc71687d..7b41997b9cb 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1508,8 +1508,6 @@ settings.authentication.form.edit=Edit 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. @@ -1541,9 +1539,9 @@ settings.authentication.github.enable_first=Enable your GitHub configuration for 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. @@ -1596,9 +1594,10 @@ settings.authentication.gitlab.form.allowUsersToSignUp.name=Allow users to sign 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 @@ -1607,7 +1606,7 @@ settings.authentication.gitlab.confirm.AUTO_PROVISIONING.description=Once you tr 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.