123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- import styled from '@emotion/styled';
- import Access from 'sentry/components/acl/access';
- import Feature from 'sentry/components/acl/feature';
- import FeatureDisabled from 'sentry/components/acl/featureDisabled';
- import {Button} from 'sentry/components/button';
- import {Hovercard} from 'sentry/components/hovercard';
- import PanelItem from 'sentry/components/panels/panelItem';
- import Tag from 'sentry/components/tag';
- import {IconLock} from 'sentry/icons';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import type {AuthProvider} from 'sentry/types';
- import type {FeatureDisabledHooks} from 'sentry/types/hooks';
- import {descopeFeatureName} from 'sentry/utils';
- type RenderInstallButtonProps = {
- hasFeature: boolean;
- /**
- * We pass the provider so that it may be passed into any hook provided
- * callbacks.
- */
- provider: AuthProvider;
- };
- type LockedFeatureProps = {
- features: string[];
- provider: AuthProvider;
- className?: string;
- };
- type FeatureRenderProps = {
- features: string[];
- hasFeature: boolean;
- renderDisabled: (p: LockedFeatureProps) => React.ReactNode;
- renderInstallButton: (p: RenderInstallButtonProps) => React.ReactNode;
- children?: (p: FeatureRenderProps) => React.ReactNode;
- };
- type Props = {
- active: boolean;
- provider: AuthProvider;
- onConfigure?: (providerKey: string, e: React.MouseEvent) => void;
- };
- function ProviderItem({provider, active, onConfigure}: Props) {
- const handleConfigure = (e: React.MouseEvent) => {
- onConfigure?.(provider.key, e);
- };
- const renderDisabledLock = (p: LockedFeatureProps) => (
- <LockedFeature provider={p.provider} features={p.features} />
- );
- const defaultRenderInstallButton = ({hasFeature}: RenderInstallButtonProps) => (
- <Access access={['org:write']}>
- {({hasAccess}) => (
- <Button
- type="submit"
- name="provider"
- size="sm"
- value={provider.key}
- disabled={!hasFeature || !hasAccess}
- onClick={handleConfigure}
- >
- {t('Configure')}
- </Button>
- )}
- </Access>
- );
- // TODO(epurkhiser): We should probably use a more explicit hook name,
- // instead of just the feature names (sso-basic, sso-saml2, etc).
- const featureKey = provider.requiredFeature;
- const hookName = featureKey
- ? (`feature-disabled:${descopeFeatureName(featureKey)}` as keyof FeatureDisabledHooks)
- : null;
- const featureProps = hookName ? {hookName} : {};
- const getProviderDescription = providerName => {
- if (providerName === 'SAML2') {
- return t(
- 'your preferred SAML2 compliant provider like Ping Identity, Google SAML, Keycloak, or VMware Identity Manager'
- );
- }
- if (providerName === 'Google') {
- return t('Google (OAuth)');
- }
- return providerName;
- };
- return (
- <Feature
- {...featureProps}
- features={[featureKey].filter(f => f)}
- renderDisabled={({children, ...props}) =>
- typeof children === 'function' &&
- // TODO(ts): the Feature component isn't correctly templatized to allow
- // for custom props in the renderDisabled function
- children({...props, renderDisabled: renderDisabledLock as any})
- }
- >
- {({
- hasFeature,
- features,
- renderDisabled,
- renderInstallButton,
- }: FeatureRenderProps) => (
- <PanelItem center>
- <ProviderInfo>
- <ProviderLogo
- className={`provider-logo ${provider.name
- .replace(/\s/g, '-')
- .toLowerCase()}`}
- />
- <div>
- <ProviderName>{provider.name}</ProviderName>
- <ProviderDescription>
- {t(
- 'Enable your organization to sign in with %s.',
- getProviderDescription(provider.name)
- )}
- </ProviderDescription>
- </div>
- </ProviderInfo>
- <FeatureBadge>
- {!hasFeature && renderDisabled({provider, features})}
- </FeatureBadge>
- <div>
- {active ? (
- <ActiveIndicator />
- ) : (
- (renderInstallButton ?? defaultRenderInstallButton)({provider, hasFeature})
- )}
- </div>
- </PanelItem>
- )}
- </Feature>
- );
- }
- export default ProviderItem;
- const ProviderInfo = styled('div')`
- flex: 1;
- display: grid;
- grid-template-columns: max-content 1fr;
- gap: ${space(2)};
- `;
- const ProviderLogo = styled('div')`
- height: 36px;
- width: 36px;
- border-radius: 3px;
- margin-right: 0;
- top: auto;
- `;
- const ProviderName = styled('div')`
- font-weight: bold;
- `;
- const ProviderDescription = styled('div')`
- font-size: ${p => p.theme.fontSizeSmall};
- color: ${p => p.theme.subText};
- `;
- const FeatureBadge = styled('div')`
- flex: 1;
- `;
- const ActiveIndicator = styled('div')`
- background: ${p => p.theme.green300};
- color: ${p => p.theme.white};
- padding: ${space(1)} ${space(1.5)};
- border-radius: 2px;
- font-size: 0.8em;
- `;
- ActiveIndicator.defaultProps = {
- children: t('Active'),
- };
- const DisabledHovercard = styled(Hovercard)`
- width: 350px;
- `;
- function LockedFeature({provider, features, className}: LockedFeatureProps) {
- return (
- <DisabledHovercard
- containerClassName={className}
- body={
- <FeatureDisabled
- features={features}
- hideHelpToggle
- message={t('%s SSO is disabled.', provider.name)}
- featureName={t('SSO Auth')}
- />
- }
- >
- <Tag role="status" icon={<IconLock isSolid />}>
- {t('disabled')}
- </Tag>
- </DisabledHovercard>
- );
- }
|