src: Rename "V2" folders to just Wizard
This replaces all occurences of "CreateImageWizardV2" with just "CreateImageWizard" as it is the only version now.
This commit is contained in:
parent
b1e5a8c7c6
commit
4fb37c187e
93 changed files with 20 additions and 22 deletions
|
|
@ -0,0 +1,63 @@
|
|||
import React, { useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
HelperText,
|
||||
HelperTextItem,
|
||||
TextInput,
|
||||
FormGroup,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
|
||||
import { useGetSourceUploadInfoQuery } from '../../../../../store/provisioningApi';
|
||||
import {
|
||||
changeAwsAccountId,
|
||||
selectAwsSourceId,
|
||||
} from '../../../../../store/wizardSlice';
|
||||
|
||||
export const AwsAccountId = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const sourceId = useAppSelector(selectAwsSourceId);
|
||||
|
||||
const { data, isError } = useGetSourceUploadInfoQuery(
|
||||
{
|
||||
id: parseInt(sourceId as string),
|
||||
},
|
||||
{ skip: sourceId === undefined || sourceId === '' }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(changeAwsAccountId(data?.aws?.account_id || ''));
|
||||
}, [data?.aws?.account_id, dispatch]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormGroup label="Associated account ID" isRequired>
|
||||
<TextInput
|
||||
readOnlyVariant="default"
|
||||
isRequired
|
||||
id="aws-account-id"
|
||||
value={sourceId && data ? data.aws?.account_id : ''}
|
||||
aria-label="aws account id"
|
||||
/>
|
||||
</FormGroup>
|
||||
<HelperText>
|
||||
<HelperTextItem component="div" variant="indeterminate">
|
||||
This is the account associated with the source.
|
||||
</HelperTextItem>
|
||||
</HelperText>
|
||||
{isError && (
|
||||
<Alert
|
||||
variant={'danger'}
|
||||
isPlain
|
||||
isInline
|
||||
title={'AWS details unavailable'}
|
||||
>
|
||||
The AWS account ID for the selected source could not be resolved.
|
||||
There might be a problem with the source. Verify that the source is
|
||||
valid in Sources or select a different source.
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
import React, { useState } from 'react';
|
||||
|
||||
import { Alert, Spinner } from '@patternfly/react-core';
|
||||
import { FormGroup } from '@patternfly/react-core';
|
||||
import {
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
} from '@patternfly/react-core/deprecated';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
|
||||
import { useGetSourceListQuery } from '../../../../../store/provisioningApi';
|
||||
import {
|
||||
changeAwsSourceId,
|
||||
selectAwsSourceId,
|
||||
} from '../../../../../store/wizardSlice';
|
||||
|
||||
export const AwsSourcesSelect = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const sourceId = useAppSelector(selectAwsSourceId);
|
||||
|
||||
const { data, isFetching, isLoading, isSuccess, isError, refetch } =
|
||||
useGetSourceListQuery({
|
||||
provider: 'aws',
|
||||
});
|
||||
|
||||
const sources = data?.data;
|
||||
const chosenSource = sources?.find((source) => source.id === sourceId);
|
||||
|
||||
const handleSelect = (
|
||||
_event: React.MouseEvent<Element, MouseEvent>,
|
||||
value: string
|
||||
) => {
|
||||
const source = sources?.find((source) => source.name === value);
|
||||
dispatch(changeAwsSourceId(source?.id));
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
dispatch(changeAwsSourceId(undefined));
|
||||
};
|
||||
|
||||
const handleToggle = () => {
|
||||
// Refetch upon opening (but not upon closing)
|
||||
if (!isOpen) {
|
||||
refetch();
|
||||
}
|
||||
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
const selectOptions = sources?.map((source) => (
|
||||
<SelectOption key={source.id} value={source.name} />
|
||||
));
|
||||
|
||||
const loadingSpinner = (
|
||||
<SelectOption key={'fetching'} isNoResultsOption={true}>
|
||||
<Spinner size="lg" />
|
||||
</SelectOption>
|
||||
);
|
||||
|
||||
if (isFetching) {
|
||||
selectOptions?.push(loadingSpinner);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormGroup isRequired label={'Source name'} data-testid="sources">
|
||||
<Select
|
||||
ouiaId="source_select"
|
||||
variant={SelectVariant.typeahead}
|
||||
onToggle={handleToggle}
|
||||
onSelect={handleSelect}
|
||||
onClear={handleClear}
|
||||
selections={chosenSource?.name}
|
||||
isOpen={isOpen}
|
||||
placeholderText="Select source"
|
||||
typeAheadAriaLabel="Select source"
|
||||
isDisabled={!isSuccess || isLoading}
|
||||
>
|
||||
{selectOptions}
|
||||
</Select>
|
||||
</FormGroup>
|
||||
<>
|
||||
{isError && (
|
||||
<Alert
|
||||
variant={'danger'}
|
||||
isPlain={true}
|
||||
isInline={true}
|
||||
title={'Sources unavailable'}
|
||||
>
|
||||
Sources cannot be reached, try again later or enter an AWS account
|
||||
ID manually.
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
import React from 'react';
|
||||
|
||||
import {
|
||||
Radio,
|
||||
Text,
|
||||
Form,
|
||||
Title,
|
||||
FormGroup,
|
||||
TextInput,
|
||||
Gallery,
|
||||
GalleryItem,
|
||||
HelperText,
|
||||
HelperTextItem,
|
||||
Button,
|
||||
} from '@patternfly/react-core';
|
||||
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
|
||||
|
||||
import { AwsAccountId } from './AwsAccountId';
|
||||
import { AwsSourcesSelect } from './AwsSourcesSelect';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
|
||||
import {
|
||||
changeAwsAccountId,
|
||||
changeAwsShareMethod,
|
||||
changeAwsSourceId,
|
||||
selectAwsAccountId,
|
||||
selectAwsShareMethod,
|
||||
} from '../../../../../store/wizardSlice';
|
||||
import { ValidatedTextInput } from '../../../ValidatedTextInput';
|
||||
import { isAwsAccountIdValid } from '../../../validators';
|
||||
|
||||
export type AwsShareMethod = 'manual' | 'sources';
|
||||
|
||||
const SourcesButton = () => {
|
||||
return (
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={<ExternalLinkAltIcon />}
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href={'settings/sources'}
|
||||
>
|
||||
Create and manage sources here
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const Aws = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const shareMethod = useAppSelector(selectAwsShareMethod);
|
||||
const shareWithAccount = useAppSelector(selectAwsAccountId);
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Title headingLevel="h1" size="xl">
|
||||
Target environment - Amazon Web Services
|
||||
</Title>
|
||||
<Text>
|
||||
Your image will be uploaded to AWS and shared with the account you
|
||||
provide below.
|
||||
</Text>
|
||||
<Text>
|
||||
<b>The shared image will expire within 14 days.</b> To permanently
|
||||
access the image, copy the image, which will be shared to your account
|
||||
by Red Hat, to your own AWS account.
|
||||
</Text>
|
||||
<FormGroup label="Share method:">
|
||||
<Radio
|
||||
id="radio-with-description"
|
||||
label="Use an account configured from Sources."
|
||||
name="radio-7"
|
||||
description="Use a configured source to launch environments directly from the console."
|
||||
isChecked={shareMethod === 'sources'}
|
||||
onChange={() => {
|
||||
dispatch(changeAwsSourceId(undefined));
|
||||
dispatch(changeAwsAccountId(''));
|
||||
dispatch(changeAwsShareMethod('sources'));
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
<Radio
|
||||
id="radio"
|
||||
label="Manually enter an account ID."
|
||||
name="radio-8"
|
||||
isChecked={shareMethod === 'manual'}
|
||||
onChange={() => {
|
||||
dispatch(changeAwsSourceId(undefined));
|
||||
dispatch(changeAwsAccountId(''));
|
||||
dispatch(changeAwsShareMethod('manual'));
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
{shareMethod === 'sources' && (
|
||||
<>
|
||||
<AwsSourcesSelect />
|
||||
<SourcesButton />
|
||||
<Gallery hasGutter>
|
||||
<GalleryItem>
|
||||
<FormGroup label="Default region" isRequired>
|
||||
<TextInput
|
||||
readOnlyVariant="default"
|
||||
isRequired
|
||||
id="someid"
|
||||
value="us-east-1"
|
||||
/>
|
||||
</FormGroup>
|
||||
<HelperText>
|
||||
<HelperTextItem component="div" variant="indeterminate">
|
||||
Images are built in the default region but can be copied to
|
||||
other regions later.
|
||||
</HelperTextItem>
|
||||
</HelperText>
|
||||
</GalleryItem>
|
||||
<GalleryItem>
|
||||
<AwsAccountId />
|
||||
</GalleryItem>
|
||||
</Gallery>
|
||||
</>
|
||||
)}
|
||||
{shareMethod === 'manual' && (
|
||||
<>
|
||||
<FormGroup label="AWS account ID" isRequired>
|
||||
<ValidatedTextInput
|
||||
ariaLabel="aws account id"
|
||||
value={shareWithAccount || ''}
|
||||
validator={isAwsAccountIdValid}
|
||||
onChange={(_event, value) => dispatch(changeAwsAccountId(value))}
|
||||
helperText="Should be 12 characters long."
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label="Default region" isRequired>
|
||||
<TextInput
|
||||
value={'us-east-1'}
|
||||
type="text"
|
||||
aria-label="default region"
|
||||
readOnlyVariant="default"
|
||||
/>
|
||||
</FormGroup>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default Aws;
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Button, FormGroup } from '@patternfly/react-core';
|
||||
|
||||
import { useAppSelector } from '../../../../../store/hooks';
|
||||
import { selectAzureTenantId } from '../../../../../store/wizardSlice';
|
||||
|
||||
export const AzureAuthButton = () => {
|
||||
const tenantId = useAppSelector(selectAzureTenantId);
|
||||
const guidRegex = new RegExp(
|
||||
'^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
|
||||
'i'
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup>
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="secondary"
|
||||
isDisabled={!guidRegex.test(tenantId)}
|
||||
href={
|
||||
'https://login.microsoftonline.com/' +
|
||||
tenantId +
|
||||
'/oauth2/v2.0/authorize?client_id=b94bb246-b02c-4985-9c22-d44e66f657f4&scope=openid&' +
|
||||
'response_type=code&response_mode=query&redirect_uri=https://portal.azure.com'
|
||||
}
|
||||
>
|
||||
Authorize Image Builder
|
||||
</Button>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
import React, { useState } from 'react';
|
||||
|
||||
import { FormGroup, Spinner } from '@patternfly/react-core';
|
||||
import {
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
} from '@patternfly/react-core/deprecated';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
|
||||
import { useGetSourceUploadInfoQuery } from '../../../../../store/provisioningApi';
|
||||
import {
|
||||
changeAzureResourceGroup,
|
||||
selectAzureResourceGroup,
|
||||
selectAzureSource,
|
||||
} from '../../../../../store/wizardSlice';
|
||||
|
||||
export const AzureResourceGroups = () => {
|
||||
const azureSource = useAppSelector(selectAzureSource);
|
||||
const azureResourceGroup = useAppSelector(selectAzureResourceGroup);
|
||||
const dispatch = useAppDispatch();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const { data: sourceDetails, isFetching } = useGetSourceUploadInfoQuery(
|
||||
{ id: parseInt(azureSource as string) },
|
||||
{
|
||||
skip: !azureSource,
|
||||
}
|
||||
);
|
||||
|
||||
const resourceGroups =
|
||||
(azureSource && sourceDetails?.azure?.resource_groups) || [];
|
||||
|
||||
const setResourceGroup = (
|
||||
_event: React.MouseEvent<Element, MouseEvent>,
|
||||
selection: string
|
||||
) => {
|
||||
const resource =
|
||||
resourceGroups?.find((resource) => resource === selection) || '';
|
||||
setIsOpen(false);
|
||||
dispatch(changeAzureResourceGroup(resource));
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
dispatch(changeAzureResourceGroup(''));
|
||||
};
|
||||
const options: JSX.Element[] = [];
|
||||
|
||||
if (isFetching) {
|
||||
options.push(
|
||||
<SelectOption
|
||||
isNoResultsOption={true}
|
||||
data-testid="azure-resource-groups-loading"
|
||||
>
|
||||
<Spinner size="lg" />
|
||||
</SelectOption>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
isRequired
|
||||
label={'Resource group'}
|
||||
data-testid="azure-resource-groups"
|
||||
>
|
||||
<Select
|
||||
ouiaId="resource_group_select"
|
||||
variant={SelectVariant.typeahead}
|
||||
onToggle={() => setIsOpen(!isOpen)}
|
||||
onSelect={setResourceGroup}
|
||||
onClear={handleClear}
|
||||
selections={azureResourceGroup}
|
||||
isOpen={isOpen}
|
||||
placeholderText="Select resource group"
|
||||
typeAheadAriaLabel="Select resource group"
|
||||
>
|
||||
{resourceGroups.map((name: string, index: number) => (
|
||||
<SelectOption
|
||||
key={index}
|
||||
value={name}
|
||||
aria-label={`Resource group ${name}`}
|
||||
/>
|
||||
))}
|
||||
</Select>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { Alert } from '@patternfly/react-core';
|
||||
import { FormGroup, Spinner } from '@patternfly/react-core';
|
||||
import {
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
} from '@patternfly/react-core/deprecated';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
|
||||
import {
|
||||
useGetSourceListQuery,
|
||||
useGetSourceUploadInfoQuery,
|
||||
} from '../../../../../store/provisioningApi';
|
||||
import {
|
||||
changeAzureResourceGroup,
|
||||
changeAzureSource,
|
||||
changeAzureSubscriptionId,
|
||||
changeAzureTenantId,
|
||||
selectAzureSource,
|
||||
} from '../../../../../store/wizardSlice';
|
||||
|
||||
export const AzureSourcesSelect = () => {
|
||||
const azureSource = useAppSelector(selectAzureSource);
|
||||
const dispatch = useAppDispatch();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const {
|
||||
data: rawSources,
|
||||
isFetching,
|
||||
isSuccess,
|
||||
isError,
|
||||
refetch,
|
||||
} = useGetSourceListQuery({ provider: 'azure' });
|
||||
|
||||
const {
|
||||
data: sourceDetails,
|
||||
isFetching: isFetchingDetails,
|
||||
isSuccess: isSuccessDetails,
|
||||
isError: isErrorDetails,
|
||||
} = useGetSourceUploadInfoQuery(
|
||||
{ id: parseInt(azureSource as string) },
|
||||
{
|
||||
skip: !azureSource,
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFetchingDetails || !isSuccessDetails) return;
|
||||
dispatch(changeAzureTenantId(sourceDetails?.azure?.tenant_id || ''));
|
||||
dispatch(
|
||||
changeAzureSubscriptionId(sourceDetails?.azure?.subscription_id || '')
|
||||
);
|
||||
}, [
|
||||
isFetchingDetails,
|
||||
isSuccessDetails,
|
||||
sourceDetails?.azure?.tenant_id,
|
||||
sourceDetails?.azure?.subscription_id,
|
||||
dispatch,
|
||||
]);
|
||||
|
||||
const handleSelect = (
|
||||
_event: React.MouseEvent<Element, MouseEvent>,
|
||||
sourceName: string
|
||||
) => {
|
||||
const sourceId = rawSources?.data?.find(
|
||||
(source) => source?.name === sourceName
|
||||
)?.id;
|
||||
dispatch(changeAzureSource(sourceId || ''));
|
||||
dispatch(changeAzureResourceGroup(''));
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
dispatch(changeAzureSource(''));
|
||||
dispatch(changeAzureTenantId(''));
|
||||
dispatch(changeAzureSubscriptionId(''));
|
||||
dispatch(changeAzureResourceGroup(''));
|
||||
};
|
||||
|
||||
const handleToggle = () => {
|
||||
// Refetch upon opening (but not upon closing)
|
||||
if (!isOpen) {
|
||||
refetch();
|
||||
}
|
||||
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
const selectOptions = rawSources?.data?.map((source) => (
|
||||
<SelectOption key={source.id} value={source.name} />
|
||||
));
|
||||
|
||||
if (isSuccess) {
|
||||
if (isFetching) {
|
||||
selectOptions?.push(
|
||||
<SelectOption key="loading" isNoResultsOption={true}>
|
||||
<Spinner size="lg" />
|
||||
</SelectOption>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormGroup isRequired label={'Source name'} data-testid="azure-sources">
|
||||
<Select
|
||||
ouiaId="source_select"
|
||||
variant={SelectVariant.typeahead}
|
||||
onToggle={handleToggle}
|
||||
onSelect={handleSelect}
|
||||
onClear={handleClear}
|
||||
selections={
|
||||
azureSource
|
||||
? rawSources?.data?.find((source) => source.id === azureSource)
|
||||
?.name
|
||||
: undefined
|
||||
}
|
||||
isOpen={isOpen}
|
||||
placeholderText="Select source"
|
||||
typeAheadAriaLabel="Select source"
|
||||
menuAppendTo="parent"
|
||||
maxHeight="25rem"
|
||||
isDisabled={!isSuccess}
|
||||
>
|
||||
{selectOptions}
|
||||
</Select>
|
||||
</FormGroup>
|
||||
{isError && (
|
||||
<Alert
|
||||
variant={'danger'}
|
||||
isPlain
|
||||
isInline
|
||||
title={'Sources unavailable'}
|
||||
>
|
||||
Sources cannot be reached, try again later or enter an account info
|
||||
for upload manually.
|
||||
</Alert>
|
||||
)}
|
||||
{!isError && isErrorDetails && (
|
||||
<Alert
|
||||
variant={'danger'}
|
||||
isPlain
|
||||
isInline
|
||||
title={'Azure details unavailable'}
|
||||
>
|
||||
Could not fetch Tenant ID and Subscription ID from Azure for given
|
||||
Source. Check Sources page for the source availability or select a
|
||||
different Source.
|
||||
</Alert>
|
||||
)}
|
||||
<></>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
import React from 'react';
|
||||
|
||||
import {
|
||||
Radio,
|
||||
Text,
|
||||
Form,
|
||||
Title,
|
||||
FormGroup,
|
||||
TextInput,
|
||||
Gallery,
|
||||
GalleryItem,
|
||||
Button,
|
||||
} from '@patternfly/react-core';
|
||||
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
|
||||
|
||||
import { AzureAuthButton } from './AzureAuthButton';
|
||||
import { AzureResourceGroups } from './AzureResourceGroups';
|
||||
import { AzureSourcesSelect } from './AzureSourcesSelect';
|
||||
|
||||
import { AZURE_AUTH_URL } from '../../../../../constants';
|
||||
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
|
||||
import {
|
||||
changeAzureResourceGroup,
|
||||
changeAzureShareMethod,
|
||||
changeAzureSource,
|
||||
changeAzureSubscriptionId,
|
||||
changeAzureTenantId,
|
||||
selectAzureResourceGroup,
|
||||
selectAzureShareMethod,
|
||||
selectAzureSubscriptionId,
|
||||
selectAzureTenantId,
|
||||
} from '../../../../../store/wizardSlice';
|
||||
import { ValidatedTextInput } from '../../../ValidatedTextInput';
|
||||
import {
|
||||
isAzureResourceGroupValid,
|
||||
isAzureSubscriptionIdValid,
|
||||
isAzureTenantGUIDValid,
|
||||
} from '../../../validators';
|
||||
|
||||
export type AzureShareMethod = 'manual' | 'sources';
|
||||
|
||||
const SourcesButton = () => {
|
||||
return (
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={<ExternalLinkAltIcon />}
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href={'settings/sources'}
|
||||
>
|
||||
Create and manage sources here
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const Azure = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const shareMethod = useAppSelector(selectAzureShareMethod);
|
||||
const tenantId = useAppSelector(selectAzureTenantId);
|
||||
const subscriptionId = useAppSelector(selectAzureSubscriptionId);
|
||||
const resourceGroup = useAppSelector(selectAzureResourceGroup);
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Title headingLevel="h1" size="xl">
|
||||
Target environment - Microsoft Azure
|
||||
</Title>
|
||||
<Text>
|
||||
Upon build, Image Builder sends the image to the selected authorized
|
||||
Azure account. The image will be uploaded to the resource group in the
|
||||
subscription you specify.
|
||||
</Text>
|
||||
<Text>
|
||||
To authorize Image Builder to push images to Microsoft Azure, the
|
||||
account owner must configure Image Builder as an authorized application
|
||||
for a specific tenant ID and give it the role of "Contributor"
|
||||
for the resource group you want to upload to. This applies even when
|
||||
defining target by Source selection.
|
||||
<br />
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
icon={<ExternalLinkAltIcon />}
|
||||
iconPosition="right"
|
||||
isInline
|
||||
href={AZURE_AUTH_URL}
|
||||
>
|
||||
Learn more about OAuth 2.0
|
||||
</Button>
|
||||
</Text>
|
||||
<FormGroup label="Share method:">
|
||||
<Radio
|
||||
id="radio-with-description"
|
||||
label="Use an account configured from Sources."
|
||||
name="radio-7"
|
||||
description="Use a configured source to launch environments directly from the console."
|
||||
isChecked={shareMethod === 'sources'}
|
||||
onChange={() => {
|
||||
dispatch(changeAzureSource(''));
|
||||
dispatch(changeAzureTenantId(''));
|
||||
dispatch(changeAzureSubscriptionId(''));
|
||||
dispatch(changeAzureShareMethod('sources'));
|
||||
dispatch(changeAzureResourceGroup(''));
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
<Radio
|
||||
id="radio"
|
||||
label="Manually enter the account information."
|
||||
name="radio-8"
|
||||
isChecked={shareMethod === 'manual'}
|
||||
onChange={() => {
|
||||
dispatch(changeAzureSource(''));
|
||||
dispatch(changeAzureTenantId(''));
|
||||
dispatch(changeAzureSubscriptionId(''));
|
||||
dispatch(changeAzureShareMethod('manual'));
|
||||
dispatch(changeAzureResourceGroup(''));
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
{shareMethod === 'sources' && (
|
||||
<>
|
||||
<AzureSourcesSelect />
|
||||
<SourcesButton />
|
||||
<Gallery hasGutter>
|
||||
<GalleryItem>
|
||||
<FormGroup label="Azure tenant GUID" isRequired>
|
||||
<TextInput
|
||||
aria-label="Azure tenant GUID"
|
||||
readOnlyVariant="default"
|
||||
isRequired
|
||||
id="tenant id"
|
||||
value={tenantId}
|
||||
/>
|
||||
</FormGroup>
|
||||
</GalleryItem>
|
||||
<GalleryItem>
|
||||
<FormGroup label="Subscription ID" isRequired>
|
||||
<TextInput
|
||||
aria-label="Subscription ID"
|
||||
label="Subscription ID"
|
||||
readOnlyVariant="default"
|
||||
isRequired
|
||||
id="subscription id"
|
||||
value={subscriptionId}
|
||||
/>
|
||||
</FormGroup>
|
||||
</GalleryItem>
|
||||
</Gallery>
|
||||
<AzureAuthButton />
|
||||
<AzureResourceGroups />
|
||||
</>
|
||||
)}
|
||||
{shareMethod === 'manual' && (
|
||||
<>
|
||||
<FormGroup label="Azure tenant GUID" isRequired>
|
||||
<ValidatedTextInput
|
||||
ariaLabel="Azure tenant GUID"
|
||||
value={tenantId || ''}
|
||||
validator={isAzureTenantGUIDValid}
|
||||
onChange={(_event, value) => dispatch(changeAzureTenantId(value))}
|
||||
helperText="Please enter a valid tenant ID"
|
||||
/>
|
||||
</FormGroup>
|
||||
<AzureAuthButton />
|
||||
<FormGroup label="Subscription ID" isRequired>
|
||||
<ValidatedTextInput
|
||||
ariaLabel="subscription id"
|
||||
value={subscriptionId}
|
||||
validator={isAzureSubscriptionIdValid}
|
||||
onChange={(_event, value) =>
|
||||
dispatch(changeAzureSubscriptionId(value))
|
||||
}
|
||||
helperText="Please enter a valid subscription ID"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label="Resource group" isRequired>
|
||||
<ValidatedTextInput
|
||||
ariaLabel="resource group"
|
||||
value={resourceGroup}
|
||||
validator={isAzureResourceGroupValid}
|
||||
onChange={(_event, value) =>
|
||||
dispatch(changeAzureResourceGroup(value))
|
||||
}
|
||||
helperText="Resource group names only allow alphanumeric characters, periods, underscores, hyphens, and parenthesis and cannot end in a period"
|
||||
/>
|
||||
</FormGroup>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default Azure;
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Radio, Text, Form, Title, FormGroup } from '@patternfly/react-core';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
|
||||
import {
|
||||
changeGcpAccountType,
|
||||
changeGcpEmail,
|
||||
changeGcpShareMethod,
|
||||
selectGcpAccountType,
|
||||
selectGcpEmail,
|
||||
selectGcpShareMethod,
|
||||
} from '../../../../../store/wizardSlice';
|
||||
import { ValidatedTextInput } from '../../../ValidatedTextInput';
|
||||
import { isGcpEmailValid } from '../../../validators';
|
||||
|
||||
export type GcpShareMethod = 'withGoogle' | 'withInsights';
|
||||
export type GcpAccountType =
|
||||
| 'user'
|
||||
| 'serviceAccount'
|
||||
| 'group'
|
||||
| 'domain'
|
||||
| undefined;
|
||||
|
||||
const Gcp = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const accountType = useAppSelector(selectGcpAccountType);
|
||||
const shareMethod = useAppSelector(selectGcpShareMethod);
|
||||
const gcpEmail = useAppSelector(selectGcpEmail);
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Title headingLevel="h1" size="xl">
|
||||
Target environment - Google Cloud Platform
|
||||
</Title>
|
||||
<Text>
|
||||
Select how to share your image. The image you create can be used to
|
||||
launch instances on GCP, regardless of which method you select.
|
||||
</Text>
|
||||
<FormGroup label="Select image sharing" isRequired>
|
||||
<Radio
|
||||
id="share-with-google"
|
||||
data-testid="share-with-google"
|
||||
label="Share image with a Google account"
|
||||
name="radio-1"
|
||||
description={
|
||||
<Text>
|
||||
Your image will be uploaded to GCP and shared with the account you
|
||||
provide below.
|
||||
<b>The image expires in 14 days.</b> To keep permanent access to
|
||||
your image, copy it to your GCP project.
|
||||
</Text>
|
||||
}
|
||||
isChecked={shareMethod === 'withGoogle'}
|
||||
onChange={() => {
|
||||
dispatch(changeGcpShareMethod('withGoogle'));
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
<Radio
|
||||
id="share-with-insights"
|
||||
data-testid="share-with-insights"
|
||||
label="Share image with Red Hat Insights only"
|
||||
name="radio-2"
|
||||
description={
|
||||
<Text>
|
||||
Your image will be uploaded to GCP and shared with Red Hat
|
||||
Insights.
|
||||
<b> The image expires in 14 days.</b> You cannot access or
|
||||
recreate this image in your GCP project.
|
||||
</Text>
|
||||
}
|
||||
isChecked={shareMethod === 'withInsights'}
|
||||
onChange={() => {
|
||||
dispatch(changeGcpShareMethod('withInsights'));
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
{shareMethod === 'withGoogle' && (
|
||||
<>
|
||||
<FormGroup label="Account type" isRequired>
|
||||
<Radio
|
||||
id="google-account"
|
||||
data-testid="google-account"
|
||||
label="Google account"
|
||||
name="radio-3"
|
||||
isChecked={accountType === 'user'}
|
||||
onChange={() => {
|
||||
dispatch(changeGcpAccountType('user'));
|
||||
}}
|
||||
/>
|
||||
<Radio
|
||||
id="service-account"
|
||||
data-testid="service-account"
|
||||
label="Service account"
|
||||
name="radio-4"
|
||||
isChecked={accountType === 'serviceAccount'}
|
||||
onChange={() => {
|
||||
dispatch(changeGcpAccountType('serviceAccount'));
|
||||
}}
|
||||
/>
|
||||
<Radio
|
||||
id="google-group"
|
||||
data-testid="google-group"
|
||||
label="Google group"
|
||||
name="radio-5"
|
||||
isChecked={accountType === 'group'}
|
||||
onChange={() => {
|
||||
dispatch(changeGcpAccountType('group'));
|
||||
}}
|
||||
/>
|
||||
<Radio
|
||||
id="google-domain"
|
||||
data-testid="google-domain"
|
||||
label="Google Workspace domain or Cloud Identity domain"
|
||||
name="radio-6"
|
||||
isChecked={accountType === 'domain'}
|
||||
onChange={() => {
|
||||
dispatch(changeGcpAccountType('domain'));
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={
|
||||
accountType === 'domain'
|
||||
? 'Domain'
|
||||
: 'Principal (e.g. e-mail address)'
|
||||
}
|
||||
isRequired
|
||||
>
|
||||
<ValidatedTextInput
|
||||
ariaLabel="google principal"
|
||||
dataTestId="principal"
|
||||
value={gcpEmail || ''}
|
||||
validator={isGcpEmailValid}
|
||||
onChange={(_event, value) => dispatch(changeGcpEmail(value))}
|
||||
helperText="Please enter a valid e-mail address."
|
||||
/>
|
||||
</FormGroup>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default Gcp;
|
||||
Loading…
Add table
Add a link
Reference in a new issue