Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/web/components/Switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import RCSwitch, { Props as RCSwitchProps } from 'rc-switch'
import Icon from './icons/Icon'

export type SwitchProps = RCSwitchProps & {
id?: string
checked?: boolean
darkMode?: boolean
offMarkup?: React.ReactNode
Expand Down
4 changes: 3 additions & 1 deletion frontend/web/components/base/forms/FieldLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Tooltip, { TooltipProps } from 'components/Tooltip'
interface FieldLabelProps {
// Associates the label with its control; required for accessibility.
htmlFor?: string
id?: string
children: ReactNode
// Shows a danger asterisk after the label.
required?: boolean
Expand All @@ -22,11 +23,12 @@ const FieldLabel: FC<FieldLabelProps> = ({
children,
className,
htmlFor,
id,
required,
tooltip,
tooltipPlace = 'top',
}) => (
<label htmlFor={htmlFor} className={cn('control-label', className)}>
<label id={id} htmlFor={htmlFor} className={cn('control-label', className)}>
{children}
{required && (
<span className='text-danger ml-1' aria-hidden>
Expand Down
2 changes: 1 addition & 1 deletion frontend/web/components/icons/GithubIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface GithubIconProps {

export const GithubIcon: React.FC<GithubIconProps> = ({
className = '',
fill = '#000000',
fill = 'currentColor',
height = 14,
width = 14,
}) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { FC, useEffect, useMemo, useState } from 'react'
import Button from 'components/base/forms/Button'
import ErrorMessage from 'components/ErrorMessage'
import Input from 'components/base/forms/Input'
import InputGroup from 'components/base/forms/InputGroup'
import Link from 'components/base/link'
import Utils from 'common/utils/utils'
import {
Repository,
Expand Down Expand Up @@ -211,14 +211,12 @@ const GithubTrustRelationshipForm: FC<GithubTrustRelationshipFormProps> = ({
/>
<div className='text-muted mb-3'>
Install the{' '}
<Button
theme='text'
className='fw-normal'
<Link
href={`/organisation/${organisationId}/integrations`}
target='_blank'
>
Flagsmith GitHub integration
</Button>{' '}
</Link>{' '}
to pick repositories from a list.
</div>
</>
Expand All @@ -227,13 +225,8 @@ const GithubTrustRelationshipForm: FC<GithubTrustRelationshipFormProps> = ({
repositoryFields = (
<InputGroup
title='Repository'
component={
<Input
className='full-width'
value={`Pinned by repository ID ${pinnedRepoId}`}
readOnly
/>
}
value={`Pinned by repository ID ${pinnedRepoId}`}
inputProps={{ className: 'full-width', readOnly: true }}
/>
)
} else if (installationId) {
Expand Down Expand Up @@ -281,9 +274,8 @@ const GithubTrustRelationshipForm: FC<GithubTrustRelationshipFormProps> = ({
<>
<InputGroup
title='Audience'
component={
<Input className='full-width' value={audience} readOnly />
}
value={audience}
inputProps={{ className: 'full-width', readOnly: true }}
/>
<WorkflowSetupSnippet
audience={audience}
Expand All @@ -308,9 +300,8 @@ const GithubTrustRelationshipForm: FC<GithubTrustRelationshipFormProps> = ({
<div className='text-right mt-4'>
<Button
onClick={save}
disabled={
(!repoFullName && !isUnresolvedPin) || isCreating || isUpdating
}
disabled={!repoFullName && !isUnresolvedPin}
isLoading={isCreating || isUpdating}
>
{isEdit ? 'Save trust relationship' : 'Create trust relationship'}
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.new-trust-relationship__provider {
border: 1px solid var(--color-border-default);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import React, { FC, useState } from 'react'
import { FC, useEffect, useRef, useState } from 'react'
import Button from 'components/base/forms/Button'
import ProviderCard from 'components/pages/organisation-settings/tabs/trust-relationships/ProviderCard'
import {
TRUST_RELATIONSHIP_PROVIDERS,
TrustRelationshipProvider,
} from 'components/pages/organisation-settings/tabs/trust-relationships/providers'
import GithubTrustRelationshipForm from 'components/pages/organisation-settings/tabs/trust-relationships/GithubTrustRelationshipForm'
import TrustRelationshipModal from 'components/pages/organisation-settings/tabs/trust-relationships/TrustRelationshipModal'
import './NewTrustRelationshipModal.scss'

type Provider = 'github' | 'other'
const ICON_SIZE = 40

type NewTrustRelationshipModalProps = {
organisationId: number
Expand All @@ -13,55 +20,70 @@ const NewTrustRelationshipModal: FC<NewTrustRelationshipModalProps> = ({
existingAudiences,
organisationId,
}) => {
const [provider, setProvider] = useState<Provider | null>(null)
const [provider, setProvider] = useState<TrustRelationshipProvider | null>(
null,
)
const formRef = useRef<HTMLDivElement>(null)

useEffect(() => {
// Land on the first field, which depends on the provider and, for GitHub, on
// whether the integration is installed.
formRef.current
?.querySelector<HTMLElement>(
'input:not([readonly]):not([type=hidden]), textarea',
)
?.focus()
}, [provider])

if (provider === 'github') {
if (!provider) {
return (
<GithubTrustRelationshipForm
organisationId={organisationId}
existingAudiences={existingAudiences}
/>
<div className='p-4'>
<p className='text-secondary mb-3'>
Choose how your CI will authenticate.
</p>
<div className='d-flex flex-column gap-3'>
{TRUST_RELATIONSHIP_PROVIDERS.map((option) => (
<ProviderCard
key={option.key}
onClick={() => setProvider(option)}
icon={option.icon(ICON_SIZE)}
title={option.label}
description={option.description}
badge={option.badge}
/>
))}
</div>
</div>
)
}
if (provider === 'other') {
return <TrustRelationshipModal organisationId={organisationId} />
}

return (
<div className='p-4'>
<div
className='panel--grey p-3 mb-3 clickable'
data-test='provider-github'
role='button'
tabIndex={0}
onClick={() => setProvider('github')}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') setProvider('github')
}}
>
<h6 className='mb-1'>GitHub Actions</h6>
<div className='text-muted fs-small'>
Let workflows in a GitHub repository authenticate with their OIDC job
token. Recommended if your CI runs on GitHub Actions.
<>
<div className='px-4 pt-4'>
<div className='fs-small text-secondary text-uppercase mb-2'>
Provider
</div>
</div>
<div
className='panel--grey p-3 clickable'
data-test='provider-other'
role='button'
tabIndex={0}
onClick={() => setProvider('other')}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') setProvider('other')
}}
>
<h6 className='mb-1'>Other OIDC provider</h6>
<div className='text-muted fs-small'>
Configure a custom issuer, audience and claim matching rules for any
OIDC identity provider, such as GitLab CI or Kubernetes.
<div className='new-trust-relationship__provider d-flex align-items-center gap-3 p-3 rounded-lg bg-surface-subtle'>
<span className='d-flex' aria-hidden>
{provider.icon(ICON_SIZE - 12)}
</span>
<div className='flex-fill fw-semibold'>{provider.label}</div>
<Button theme='text' onClick={() => setProvider(null)}>
Change
</Button>
</div>
</div>
</div>
<div ref={formRef}>
{provider.key === 'github' ? (
<GithubTrustRelationshipForm
organisationId={organisationId}
existingAudiences={existingAudiences}
/>
) : (
<TrustRelationshipModal organisationId={organisationId} />
)}
</div>
</>
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Layout, spacing and radius come from utilities; this is what they cannot
// express. `border-1` is not an option here: it uses a black alpha, so it all
// but disappears in dark mode.
.provider-card {
border: 1px solid var(--color-border-default);

&:hover {
border-color: var(--color-border-strong);
background: var(--color-surface-subtle);
}

&__body {
min-width: 0;
}

&__title {
font-weight: var(--font-weight-medium);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { FC, ReactNode } from 'react'
import { colorIconSecondary } from 'common/theme/tokens'
import BareButton from 'components/base/forms/BareButton'
import Chip from 'components/base/Chip'
import Icon from 'components/icons/Icon'
import './ProviderCard.scss'
Comment thread
coderabbitai[bot] marked this conversation as resolved.

export type ProviderCardProps = {
icon: ReactNode
title: string
description: string
badge?: string
onClick: () => void
}

const ProviderCard: FC<ProviderCardProps> = ({
badge,
description,
icon,
onClick,
title,
}) => (
<BareButton
className='provider-card d-flex align-items-center gap-3 w-100 p-3 text-start rounded-xl transition-fast'
onClick={onClick}
>
<span
className='d-flex align-items-center justify-content-center flex-shrink-0 p-2 rounded-lg bg-surface-muted'
aria-hidden
>
{icon}
</span>
<span className='provider-card__body d-flex flex-column gap-1 flex-fill'>
<span className='d-flex align-items-center gap-2 flex-wrap'>
<span className='provider-card__title'>{title}</span>
{!!badge && (
<Chip size='xs' variant='accent'>
{badge}
</Chip>
)}
</span>
<span className='fs-small text-secondary'>{description}</span>
</span>
<span className='d-flex align-items-center flex-shrink-0' aria-hidden>
<Icon name='chevron-right' width={20} fill={colorIconSecondary} />
</span>
</BareButton>
)

export default ProviderCard
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from './ProviderCard'
export type { ProviderCardProps } from './ProviderCard'
Loading
Loading