Skip to content

Commit

Permalink
[fix] Azure: add multiple subscriptions via management groups guide (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
sijav authored Jul 16, 2024
1 parent 78e6ef3 commit 309a745
Show file tree
Hide file tree
Showing 10 changed files with 458 additions and 332 deletions.
16 changes: 11 additions & 5 deletions src/assets/azure-config-instructions/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { env } from 'src/shared/constants'
const gcpUploadConfigInstructionsAssetsURL = `${env.imagesAssetsUrl}/azure-config-instructions`
const gcpUploadConfigInstructionsAssetsURL = `${env.imagesAssetsUrl}/azure-config-instructions/20240715T164452`

const instructionImage1 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction1.png`, width: 1332, height: 432 }
const instructionImage2 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction2.png`, width: 542, height: 476 }
Expand All @@ -14,16 +14,19 @@ const instructionImage10 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instr
const instructionImage11 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction11.png`, width: 2006, height: 1240 }
const instructionImage12 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction12.png`, width: 2084, height: 1488 }
const instructionImage13 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction13.png`, width: 702, height: 262 }
const instructionImage14 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction14.png`, width: 1326, height: 420 }
const instructionImage15 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction15.png`, width: 730, height: 334 }
const instructionImage16 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction16.png`, width: 690, height: 862 }
const instructionImage14 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction14.png`, width: 1410, height: 464 }
const instructionImage15 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction15.png`, width: 1704, height: 642 }
const instructionImage16 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction16.png`, width: 1010, height: 664 }
const instructionImage17 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction17.png`, width: 454, height: 354 }
const instructionImage18 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction18.png`, width: 2332, height: 1044 }
const instructionImage18 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction18.png`, width: 1720, height: 1026 }
const instructionImage19 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction19.png`, width: 970, height: 134 }
const instructionImage20 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction20.png`, width: 1044, height: 978 }
const instructionImage21 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction21.png`, width: 1054, height: 1466 }
const instructionImage22 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction22.png`, width: 1044, height: 1458 }
const instructionImage23 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction23.png`, width: 1820, height: 1672 }
const instructionImage24 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction24.png`, width: 2326, height: 972 }
const instructionImage25 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction25.png`, width: 1038, height: 134 }
const instructionImage26 = { src: `${gcpUploadConfigInstructionsAssetsURL}/instruction26.png`, width: 2736, height: 1686 }

export {
instructionImage1,
Expand All @@ -42,6 +45,9 @@ export {
instructionImage21,
instructionImage22,
instructionImage23,
instructionImage24,
instructionImage25,
instructionImage26,
instructionImage3,
instructionImage4,
instructionImage5,
Expand Down
178 changes: 105 additions & 73 deletions src/locales/de-DE/messages.po

Large diffs are not rendered by default.

192 changes: 112 additions & 80 deletions src/locales/en-US/messages.po

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { Trans, t } from '@lingui/macro'
import { Alert, AlertTitle, Skeleton, Stack, Typography } from '@mui/material'
import { useMutation, useQueryClient, useSuspenseQuery } from '@tanstack/react-query'
import { AxiosError } from 'axios'
import { Dispatch, FormEvent, PropsWithChildren, ReactNode, SetStateAction, useEffect, useRef } from 'react'
import { useUserProfile } from 'src/core/auth'
import { useSnackbar } from 'src/core/snackbar'
import { PutWorkspaceCloudAccountAzureCredentialsErrorResponse } from 'src/shared/types/server'
import { WorkspaceSettingsAccountsSetupCloudAzureSubmitCredentialsInput } from './WorkspaceSettingsAccountsSetupCloudAzureSubmitCredentialsInput'
import { getWorkspaceCloudAccountAzureCredentialsQuery } from './getWorkspaceCloudAccountAzureCredentials.query'
import { putWorkspaceCloudAccountAzureKeyMutation } from './putWorkspaceCloudAccountAzureCredentials.mutation'

interface WorkspaceSettingsAccountsSetupCloudAzureFormProps extends PropsWithChildren {
isMobile?: boolean
submitButton: ReactNode
hasError: boolean
setHasError: Dispatch<SetStateAction<boolean>>
setIsPending: Dispatch<SetStateAction<boolean>>
}

type AzureFormNames = 'azure_tenant_id' | 'client_id' | 'client_secret'

const errorResponseToMessage = (error?: AxiosError<PutWorkspaceCloudAccountAzureCredentialsErrorResponse>) => {
const detail = error?.response?.data?.detail
switch (detail) {
case 'invalid_credentials':
return t`Incorrect credentials. Please carefully review and re-enter your Application ID, Directory ID, and Secret Value correctly, then submit again.`
}
return detail
}

export const WorkspaceSettingsAccountsSetupCloudAzureForm = ({
isMobile,
hasError,
children,
submitButton,
setHasError,
setIsPending,
}: WorkspaceSettingsAccountsSetupCloudAzureFormProps) => {
const { selectedWorkspace } = useUserProfile()
const queryClient = useQueryClient()
const { showSnackbar } = useSnackbar()
const { data } = useSuspenseQuery({
queryKey: ['workspace-cloud-account-azure-credentials', selectedWorkspace?.id],
queryFn: getWorkspaceCloudAccountAzureCredentialsQuery,
})
const formData = useRef<Record<AzureFormNames, { hasError: boolean; value: string }>>({
azure_tenant_id: { hasError: true, value: '' },
client_id: { hasError: true, value: '' },
client_secret: { hasError: true, value: '' },
})
const { mutate, isPending, error, isSuccess } = useMutation({
mutationFn: putWorkspaceCloudAccountAzureKeyMutation,
onError: () => {
setHasError(true)
},
onSuccess: () => {
void showSnackbar(t`Done! We will now import your Azure accounts, this usually takes a couple of minutes.`, { severity: 'success' })
},
onSettled: () => {
void queryClient.invalidateQueries({
predicate: (query) => query.queryKey[0] === 'workspace-cloud-account-azure-credentials',
})
},
})
useEffect(() => {
setIsPending(isPending)
}, [isPending, setIsPending])

const handleSubmit = (e?: FormEvent<HTMLFormElement>) => {
e?.preventDefault()
if (!hasError) {
const workspaceId = selectedWorkspace?.id
const azure_tenant_id = formData.current.azure_tenant_id.value
const client_id = formData.current.client_id.value
const client_secret = formData.current.client_secret.value

if (workspaceId && azure_tenant_id && client_id && client_secret) {
mutate({
azure_tenant_id,
client_id,
client_secret,
workspaceId,
})
}
}
}

const errorMessageDetail = errorResponseToMessage(error as AxiosError<PutWorkspaceCloudAccountAzureCredentialsErrorResponse>)

const handleChange = (name: AzureFormNames, value: string) => {
formData.current[name].value = value
}

const handleError = (name: AzureFormNames, error?: string) => {
formData.current[name].hasError = !!error
setHasError(!!Object.values(formData.current).find((i) => i.hasError))
}

return (
<Stack
direction={isMobile ? 'column-reverse' : 'row'}
spacing={3}
component="form"
onSubmit={handleSubmit}
noValidate
autoComplete="off"
>
{selectedWorkspace?.id && data ? (
isSuccess ? (
<Stack width={isMobile ? '100%' : '50%'} justifyContent="center" alignItems="center">
<Typography maxWidth={400}>
<Trans>Done! We will now import your Azure accounts, this usually takes a couple of minutes.</Trans>
</Typography>
</Stack>
) : (
<Stack width={isMobile ? '100%' : '50%'} spacing={1} justifyContent="center" alignItems="center">
<WorkspaceSettingsAccountsSetupCloudAzureSubmitCredentialsInput
name="client_id"
label={t`Application (client) ID`}
onChange={handleChange}
onError={handleError}
disabled={isPending}
uuidRegex
/>
<WorkspaceSettingsAccountsSetupCloudAzureSubmitCredentialsInput
name="azure_tenant_id"
label={t`Directory (tenant) ID`}
onChange={handleChange}
onError={handleError}
disabled={isPending}
uuidRegex
/>
<WorkspaceSettingsAccountsSetupCloudAzureSubmitCredentialsInput
name="client_secret"
label={t`Secret Value`}
onChange={handleChange}
onError={handleError}
disabled={isPending}
/>
{errorMessageDetail ? (
<Alert severity="error" sx={{ width: 500, maxWidth: '100%' }}>
<AlertTitle>
<Trans>Submission Error</Trans>:
</AlertTitle>
{errorMessageDetail}
</Alert>
) : null}
{submitButton}
</Stack>
)
) : (
<Skeleton height="100%" width="100%" />
)}
{children}
</Stack>
)
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Trans } from '@lingui/macro'
import { Button, Skeleton, Stack, Step, StepContent, StepLabel, Stepper, Typography, styled } from '@mui/material'
import { useEffect, useRef } from 'react'
import { PropsWithChildren, useEffect, useRef } from 'react'
import { LazyLoadImage } from 'react-lazy-load-image-component'
import { panelUI } from 'src/shared/constants'
import { useNonce } from 'src/shared/providers'
import { usePersistState } from 'src/shared/utils/usePersistState'
import { getInstructions, maxInstructionImageWidth } from './getInstructions'

interface WorkspaceSettingsAccountsSetupCloudAzureInstructionsProps {
interface WorkspaceSettingsAccountsSetupCloudAzureInstructionsProps extends PropsWithChildren {
isMobile: boolean
}

Expand All @@ -22,6 +22,7 @@ const StyledImage = styled(LazyLoadImage)(({ theme, width }) => ({

export const WorkspaceSettingsAccountsSetupCloudAzureInstructions = ({
isMobile,
children,
}: WorkspaceSettingsAccountsSetupCloudAzureInstructionsProps) => {
const nonce = useNonce()
const instructions = getInstructions(isMobile)
Expand Down Expand Up @@ -59,7 +60,11 @@ export const WorkspaceSettingsAccountsSetupCloudAzureInstructions = ({
<Stepper activeStep={activeStep} orientation="vertical">
{instructions.map(({ instruction, label, divComponent, image }, index) => (
<Step key={index}>
<StepLabel id={`workspace-settings-accounts-setup-cloud-gcp-label-${index}`} onClick={() => setActiveStep(index)}>
<StepLabel
id={`workspace-settings-accounts-setup-cloud-gcp-label-${index}`}
onClick={() => setActiveStep(index)}
sx={activeStep === index ? undefined : { cursor: 'pointer!important' }}
>
{label}
</StepLabel>
<StepContent transitionDuration={panelUI.fastTooltipDelay}>
Expand Down Expand Up @@ -91,7 +96,9 @@ export const WorkspaceSettingsAccountsSetupCloudAzureInstructions = ({
<Button onClick={() => setActiveStep(index + 1)} variant="contained">
<Trans>Continue</Trans>
</Button>
) : null}
) : (
children
)}
{index ? (
<Button onClick={() => setActiveStep(index - 1)}>
<Trans>Back</Trans>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
import { Skeleton, Stack, Theme, useMediaQuery } from '@mui/material'
import { Suspense } from 'react'
import { Trans } from '@lingui/macro'
import SendIcon from '@mui/icons-material/Send'
import { LoadingButton } from '@mui/lab'
import { Theme, useMediaQuery } from '@mui/material'
import { Suspense, useState } from 'react'
import { FullPageLoadingSuspenseFallback } from 'src/shared/loading'
import { WorkspaceSettingsAccountsSetupCloudAzureForm } from './WorkspaceSettingsAccountsSetupCloudAzureForm'
import { WorkspaceSettingsAccountsSetupCloudAzureInstructions } from './WorkspaceSettingsAccountsSetupCloudAzureInstructions'
import { WorkspaceSettingsAccountsSetupCloudAzureSubmitCredentials } from './WorkspaceSettingsAccountsSetupCloudAzureSubmitCredentials'

export default function WorkspaceSettingsAccountsSetupCloudAzurePage() {
const isMobile = useMediaQuery<Theme>((theme) => theme.breakpoints.down('lg'))
const [hasError, setHasError] = useState(true)
const [isPending, setIsPending] = useState(true)

const submitButton = (
<LoadingButton type="submit" variant="contained" loading={isPending} loadingPosition="end" endIcon={<SendIcon />} disabled={hasError}>
<Trans>Submit</Trans>
</LoadingButton>
)

return (
<Stack direction={isMobile ? 'column-reverse' : 'row'} spacing={1}>
<Suspense
fallback={
<Stack width={isMobile ? '100%' : '50%'} spacing={1} justifyContent="center" alignItems="center">
<Skeleton width="100%" height="100%" variant="rounded" />
</Stack>
}
<Suspense fallback={<FullPageLoadingSuspenseFallback />}>
<WorkspaceSettingsAccountsSetupCloudAzureForm
isMobile={isMobile}
hasError={hasError}
setIsPending={setIsPending}
setHasError={setHasError}
submitButton={submitButton}
>
<WorkspaceSettingsAccountsSetupCloudAzureSubmitCredentials isMobile={isMobile} />
</Suspense>
<WorkspaceSettingsAccountsSetupCloudAzureInstructions isMobile={isMobile} />
</Stack>
<WorkspaceSettingsAccountsSetupCloudAzureInstructions isMobile={isMobile}>
{submitButton}
</WorkspaceSettingsAccountsSetupCloudAzureInstructions>
</WorkspaceSettingsAccountsSetupCloudAzureForm>
</Suspense>
)
}
Loading

0 comments on commit 309a745

Please sign in to comment.