mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-10-30 14:49:14 +00:00
212 lines
5.5 KiB
TypeScript
212 lines
5.5 KiB
TypeScript
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>
|
|
);
|
|
}
|