Remove old implementation of create image wizard
We don't need the old implementation anymore, this PR fully covers what the previous steps did
This commit is contained in:
parent
9a56b859ef
commit
ace808943f
16 changed files with 4 additions and 1320 deletions
|
|
@ -1,368 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { withRouter } from 'react-router-dom';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { actions } from '../../store/actions';
|
|
||||||
|
|
||||||
import { Button, Wizard } from '@patternfly/react-core';
|
|
||||||
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
|
|
||||||
import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux';
|
|
||||||
|
|
||||||
import WizardStepImageOutput from './WizardStepImageOutput';
|
|
||||||
import WizardStepUploadAWS from './WizardStepUploadAWS';
|
|
||||||
import WizardStepUploadAzure from './WizardStepUploadAzure';
|
|
||||||
import WizardStepPackages from './WizardStepPackages';
|
|
||||||
import WizardStepUploadGoogle from './WizardStepUploadGoogle';
|
|
||||||
import WizardStepRegistration from './WizardStepRegistration';
|
|
||||||
import WizardStepReview from './WizardStepReview';
|
|
||||||
import ImageWizardFooter from './ImageWizardFooter';
|
|
||||||
|
|
||||||
import './CreateImageWizard.scss';
|
|
||||||
|
|
||||||
class CreateImageWizard extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.onStep = this.onStep.bind(this);
|
|
||||||
this.onSave = this.onSave.bind(this);
|
|
||||||
this.onClose = this.onClose.bind(this);
|
|
||||||
this.validate = this.validate.bind(this);
|
|
||||||
this.validateUploadAmazon = this.validateUploadAmazon.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
/* errors take form of $fieldId: error */
|
|
||||||
uploadAWSErrors: {},
|
|
||||||
uploadAzureErrors: {},
|
|
||||||
uploadGoogleErrors: {},
|
|
||||||
isSaveInProgress: false,
|
|
||||||
isValidSubscription: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidMount() {
|
|
||||||
let user = await insights.chrome.auth.getUser();
|
|
||||||
this.setState({
|
|
||||||
subscription: {
|
|
||||||
organization: Number(user.identity.internal.org_id)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onStep(step) {
|
|
||||||
if (step.name === 'Review') {
|
|
||||||
this.validate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validate() {
|
|
||||||
/* upload */
|
|
||||||
Object.keys(this.props.uploadDestinations).forEach(provider => {
|
|
||||||
switch (provider) {
|
|
||||||
case 'aws':
|
|
||||||
this.validateUploadAmazon();
|
|
||||||
break;
|
|
||||||
case 'azure':
|
|
||||||
this.validateUploadAzure();
|
|
||||||
break;
|
|
||||||
case 'google':
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
/* subscription */
|
|
||||||
if (this.props.subscribeNow) {
|
|
||||||
this.setState({ isValidSubscription: this.props.subscription.activationKey ? true : false });
|
|
||||||
} else {
|
|
||||||
this.setState({ isValidSubscription: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validateUploadAmazon() {
|
|
||||||
let uploadAWSErrors = {};
|
|
||||||
let share = this.props.uploadAWS.shareWithAccounts;
|
|
||||||
if (share.length === 0 || share[0].length !== 12 || isNaN(share[0])) {
|
|
||||||
uploadAWSErrors['aws-account-id'] =
|
|
||||||
{ label: 'AWS account ID', value: 'A 12-digit number is required' };
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ uploadAWSErrors });
|
|
||||||
}
|
|
||||||
|
|
||||||
validateUploadAzure() {
|
|
||||||
let uploadAzureErrors = {};
|
|
||||||
|
|
||||||
let tenant_id = this.props.uploadAzure.tenantId;
|
|
||||||
if (tenant_id === null || tenant_id === '') {
|
|
||||||
uploadAzureErrors['azure-resource-group'] =
|
|
||||||
{ label: 'Azure tenant ID', value: 'A tenant ID is required' };
|
|
||||||
}
|
|
||||||
|
|
||||||
let subscriptionId = this.props.uploadAzure.subscriptionId;
|
|
||||||
if (subscriptionId === null || subscriptionId === '') {
|
|
||||||
uploadAzureErrors['azure-subscription-id'] =
|
|
||||||
{ label: 'Azure subscription ID', value: 'A subscription ID is required' };
|
|
||||||
}
|
|
||||||
|
|
||||||
let resource_group = this.props.uploadAzure.resourceGroup;
|
|
||||||
if (resource_group === null || resource_group === '') {
|
|
||||||
uploadAzureErrors['azure-resource-group'] =
|
|
||||||
{ label: 'Azure resource group', value: 'A resource group is required' };
|
|
||||||
}
|
|
||||||
// TODO check oauth2 thing too here?
|
|
||||||
}
|
|
||||||
|
|
||||||
onSave() {
|
|
||||||
this.setState({ isSaveInProgress: true });
|
|
||||||
|
|
||||||
let customizations = {
|
|
||||||
packages: this.props.selectedPackages.map(p => p.name),
|
|
||||||
};
|
|
||||||
if (this.props.subscribeNow) {
|
|
||||||
customizations.subscription = {
|
|
||||||
'activation-key': this.props.subscription.activationKey,
|
|
||||||
insights: this.props.subscription.insights,
|
|
||||||
organization: Number(this.props.subscription.organization),
|
|
||||||
'server-url': 'subscription.rhsm.redhat.com',
|
|
||||||
'base-url': 'https://cdn.redhat.com/',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let requests = [];
|
|
||||||
if (this.props.uploadDestinations.aws) {
|
|
||||||
let request = {
|
|
||||||
distribution: this.props.release.distro,
|
|
||||||
image_requests: [
|
|
||||||
{
|
|
||||||
architecture: this.props.release.arch,
|
|
||||||
image_type: 'ami',
|
|
||||||
upload_request: {
|
|
||||||
type: 'aws',
|
|
||||||
options: {
|
|
||||||
share_with_accounts: this.props.uploadAWS.shareWithAccounts,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
customizations,
|
|
||||||
};
|
|
||||||
requests.push(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.uploadDestinations.google) {
|
|
||||||
let share = '';
|
|
||||||
switch (this.props.uploadGoogle.accountType) {
|
|
||||||
case 'googleAccount':
|
|
||||||
share = 'user:' + this.props.uploadGoogle.shareWithAccounts[0].user;
|
|
||||||
break;
|
|
||||||
case 'serviceAccount':
|
|
||||||
share = 'serviceAccount:' + this.props.uploadGoogle.shareWithAccounts[0].serviceAccount;
|
|
||||||
break;
|
|
||||||
case 'googleGroup':
|
|
||||||
share = 'group:' + this.props.uploadGoogle.shareWithAccounts[0].group;
|
|
||||||
break;
|
|
||||||
case 'domain':
|
|
||||||
share = 'domain:' + this.props.uploadGoogle.shareWithAccounts[0].domain;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = {
|
|
||||||
distribution: this.props.release.distro,
|
|
||||||
image_requests: [
|
|
||||||
{
|
|
||||||
architecture: this.props.release.arch,
|
|
||||||
image_type: 'vhd',
|
|
||||||
upload_request: {
|
|
||||||
type: 'gcp',
|
|
||||||
options: {
|
|
||||||
share_with_accounts: [ share ],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
customizations,
|
|
||||||
};
|
|
||||||
|
|
||||||
requests.push(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.uploadDestinations.azure) {
|
|
||||||
let request = {
|
|
||||||
distribution: this.props.release.distro,
|
|
||||||
image_requests: [
|
|
||||||
{
|
|
||||||
architecture: this.props.release.arch,
|
|
||||||
image_type: 'vhd',
|
|
||||||
upload_request: {
|
|
||||||
type: 'azure',
|
|
||||||
options: {
|
|
||||||
tenant_id: this.props.uploadAzure.tenantId,
|
|
||||||
subscription_id: this.props.uploadAzure.subscriptionId,
|
|
||||||
resource_group: this.props.uploadAzure.resourceGroup,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
customizations,
|
|
||||||
};
|
|
||||||
requests.push(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
const composeRequests = requests.map(request => this.props.composeStart(request));
|
|
||||||
|
|
||||||
Promise.all(composeRequests)
|
|
||||||
.then(() => {
|
|
||||||
if (!this.props.composesError) {
|
|
||||||
this.props.addNotification({
|
|
||||||
variant: 'success',
|
|
||||||
title: 'Your image is being created',
|
|
||||||
});
|
|
||||||
this.props.history.push('/landing');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ isSaveInProgress: false });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose () {
|
|
||||||
this.props.history.push('/landing');
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const isValidUploadDestination = this.props.uploadDestinations.aws ||
|
|
||||||
this.props.uploadDestinations.azure ||
|
|
||||||
this.props.uploadDestinations.google;
|
|
||||||
|
|
||||||
const StepImageOutput = {
|
|
||||||
name: 'Image output',
|
|
||||||
component: <WizardStepImageOutput />
|
|
||||||
};
|
|
||||||
|
|
||||||
const StepUploadAWS = {
|
|
||||||
name: 'Amazon Web Services',
|
|
||||||
component: <WizardStepUploadAWS
|
|
||||||
errors={ this.state.uploadAWSErrors } />
|
|
||||||
};
|
|
||||||
|
|
||||||
const StepUploadAzure = {
|
|
||||||
name: 'Microsoft Azure',
|
|
||||||
component: <WizardStepUploadAzure
|
|
||||||
errors={ this.state.uploadAzureErrors } />
|
|
||||||
};
|
|
||||||
|
|
||||||
const StepUploadGoogle = {
|
|
||||||
name: 'Google Cloud Platform',
|
|
||||||
component: <WizardStepUploadGoogle
|
|
||||||
errors={ this.state.uploadGoogleErrors } />
|
|
||||||
};
|
|
||||||
|
|
||||||
const uploadDestinationSteps = [];
|
|
||||||
if (this.props.uploadDestinations.aws) {
|
|
||||||
uploadDestinationSteps.push(StepUploadAWS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.uploadDestinations.azure) {
|
|
||||||
uploadDestinationSteps.push(StepUploadAzure);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.uploadDestinations.google) {
|
|
||||||
uploadDestinationSteps.push(StepUploadGoogle);
|
|
||||||
}
|
|
||||||
|
|
||||||
const StepTargetEnv = {
|
|
||||||
name: 'Target environment',
|
|
||||||
steps: uploadDestinationSteps
|
|
||||||
};
|
|
||||||
|
|
||||||
const steps = [
|
|
||||||
StepImageOutput,
|
|
||||||
...(StepTargetEnv.steps.length > 0 ? [ StepTargetEnv ] : []),
|
|
||||||
{
|
|
||||||
name: 'Registration',
|
|
||||||
component: <WizardStepRegistration
|
|
||||||
isValidSubscription={ this.state.isValidSubscription } /> },
|
|
||||||
{
|
|
||||||
name: 'Packages',
|
|
||||||
component: <WizardStepPackages /> },
|
|
||||||
{
|
|
||||||
name: 'Review',
|
|
||||||
component: <WizardStepReview
|
|
||||||
uploadAWSErrors={ this.state.uploadAWSErrors }
|
|
||||||
isValidSubscription={ this.state.isValidSubscription } />,
|
|
||||||
nextButtonText: 'Create',
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<Wizard
|
|
||||||
className="image-builder"
|
|
||||||
title={ 'Create image' }
|
|
||||||
description={ <>
|
|
||||||
Create a RHEL image and push it to cloud providers.
|
|
||||||
{' '}
|
|
||||||
<Button
|
|
||||||
component="a"
|
|
||||||
target="_blank"
|
|
||||||
variant="link"
|
|
||||||
icon={ <ExternalLinkAltIcon /> }
|
|
||||||
iconPosition="right"
|
|
||||||
isInline
|
|
||||||
href="
|
|
||||||
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/uploading_a_customized_rhel_system_image_to_cloud_environments/index
|
|
||||||
">
|
|
||||||
Documentation
|
|
||||||
</Button>
|
|
||||||
</> }
|
|
||||||
onNext={ this.onStep }
|
|
||||||
onGoToStep={ this.onStep }
|
|
||||||
steps={ steps }
|
|
||||||
onClose={ this.onClose }
|
|
||||||
onSave={ this.onSave }
|
|
||||||
footer={ <ImageWizardFooter
|
|
||||||
isValidUploadDestination={ isValidUploadDestination }
|
|
||||||
isSaveInProgress={ this.state.isSaveInProgress }
|
|
||||||
isValidSubscription={ this.state.isValidSubscription }
|
|
||||||
error={ this.props.composesError } /> }
|
|
||||||
isOpen />
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
composesError: state.composes.error,
|
|
||||||
release: state.pendingCompose.release,
|
|
||||||
uploadDestinations: state.pendingCompose.uploadDestinations,
|
|
||||||
uploadAWS: state.pendingCompose.uploadAWS,
|
|
||||||
uploadAzure: state.pendingCompose.uploadAzure,
|
|
||||||
uploadGoogle: state.pendingCompose.uploadGoogle,
|
|
||||||
selectedPackages: state.pendingCompose.selectedPackages,
|
|
||||||
subscription: state.pendingCompose.subscription,
|
|
||||||
subscribeNow: state.pendingCompose.subscribeNow,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
composeUpdated: (compose) => dispatch(actions.composeUpdated(compose)),
|
|
||||||
composeStart: (composeRequest) => dispatch(actions.composeStart(composeRequest)),
|
|
||||||
addNotification: (not) => dispatch(addNotification(not)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateImageWizard.propTypes = {
|
|
||||||
composesError: PropTypes.string,
|
|
||||||
composeUpdated: PropTypes.func,
|
|
||||||
composeStart: PropTypes.func,
|
|
||||||
addNotification: PropTypes.func,
|
|
||||||
history: PropTypes.object,
|
|
||||||
release: PropTypes.object,
|
|
||||||
uploadDestinations: PropTypes.object,
|
|
||||||
uploadAWS: PropTypes.object,
|
|
||||||
uploadAzure: PropTypes.object,
|
|
||||||
uploadGoogle: PropTypes.object,
|
|
||||||
subscription: PropTypes.object,
|
|
||||||
subscribeNow: PropTypes.bool,
|
|
||||||
selectedPackages: PropTypes.array,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(CreateImageWizard));
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { Button, ButtonVariant, Text, TextContent, WizardContextConsumer, WizardFooter } from '@patternfly/react-core';
|
|
||||||
import { ExclamationCircleIcon } from '@patternfly/react-icons';
|
|
||||||
|
|
||||||
import './ImageWizardFooter.scss';
|
|
||||||
|
|
||||||
const ImageWizardFooter = (props) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<WizardFooter>
|
|
||||||
<WizardContextConsumer>
|
|
||||||
{({ activeStep, onNext, onBack, onClose }) => {
|
|
||||||
let nextButtonText = 'Next';
|
|
||||||
if (activeStep.name === 'Review') {
|
|
||||||
nextButtonText = props.isSaveInProgress ? 'Creating...' : 'Create';
|
|
||||||
}
|
|
||||||
|
|
||||||
let nextButtonIsDisabled = props.isSaveInProgress;
|
|
||||||
|
|
||||||
if ((activeStep.name === 'Image output' || activeStep.name === 'Review') && !props.isValidUploadDestination) {
|
|
||||||
nextButtonIsDisabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((activeStep.name === 'Registration' || activeStep.name === 'Review') && !props.isValidSubscription) {
|
|
||||||
nextButtonIsDisabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button aria-label={ activeStep.name === 'Review' ? 'Create' : 'Next' } variant={ ButtonVariant.primary }
|
|
||||||
onClick={ onNext } isDisabled={ nextButtonIsDisabled }>
|
|
||||||
{ nextButtonText }
|
|
||||||
</Button>
|
|
||||||
<Button aria-label="Back" variant={ ButtonVariant.secondary }
|
|
||||||
onClick={ onBack } isDisabled={ props.isSaveInProgress || activeStep.name === 'Image output' }>
|
|
||||||
Back
|
|
||||||
</Button>
|
|
||||||
<Button aria-label="Cancel" variant={ ButtonVariant.link }
|
|
||||||
onClick={ onClose } isDisabled={ props.isSaveInProgress }>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</WizardContextConsumer>
|
|
||||||
{ props.error && (
|
|
||||||
<TextContent className="footer-error">
|
|
||||||
<Text><ExclamationCircleIcon /> <strong>{props.error}</strong></Text>
|
|
||||||
</TextContent>
|
|
||||||
)}
|
|
||||||
</WizardFooter>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ImageWizardFooter.propTypes = {
|
|
||||||
isValidUploadDestination: PropTypes.bool,
|
|
||||||
isSaveInProgress: PropTypes.bool,
|
|
||||||
isValidSubscription: PropTypes.bool,
|
|
||||||
error: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ImageWizardFooter;
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
.footer-error {
|
|
||||||
flex-basis: 100%;
|
|
||||||
color: var(--pf-global--palette--red-100);
|
|
||||||
}
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { actions } from '../../store/actions';
|
|
||||||
|
|
||||||
import { Form, FormGroup, FormSelect, FormSelectOption, Tile, Title } from '@patternfly/react-core';
|
|
||||||
|
|
||||||
import './WizardStepImageOutput.scss';
|
|
||||||
|
|
||||||
class WizardStepImageOutput extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.setDistro = this.setDistro.bind(this);
|
|
||||||
this.toggleUploadDestination = this.toggleUploadDestination.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
setDistro(distro) {
|
|
||||||
this.props.setRelease({ arch: 'x86_64', distro });
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleUploadDestination(provider) {
|
|
||||||
this.props.setUploadDestinations({
|
|
||||||
...this.props.uploadDestinations,
|
|
||||||
[provider]: !this.props.uploadDestinations[provider]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const releaseOptions = [
|
|
||||||
{ value: 'rhel-84', label: 'Red Hat Enterprise Linux (RHEL) 8' },
|
|
||||||
{ value: 'centos-8', label: 'CentOS Stream 8' },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Form>
|
|
||||||
<Title headingLevel="h2" size="xl">Image output</Title>
|
|
||||||
<FormGroup isRequired label="Release" fieldId="release-select">
|
|
||||||
<FormSelect value={ this.props.release.distro } onChange={ value => this.setDistro(value) } isRequired
|
|
||||||
aria-label="Select release input" id="release-select" data-testid="release-select">
|
|
||||||
{ releaseOptions.map(option => <FormSelectOption key={ option.value } value={ option.value } label={ option.label } />) }
|
|
||||||
</FormSelect>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup isRequired label="Select target environment" data-testid="target-select">
|
|
||||||
<div className="tiles">
|
|
||||||
<Tile
|
|
||||||
className="tile pf-u-mr-sm"
|
|
||||||
data-testid="upload-aws"
|
|
||||||
title="Amazon Web Services"
|
|
||||||
icon={ <img
|
|
||||||
className='provider-icon'
|
|
||||||
src={ '/apps/frontend-assets/partners-icons/aws.svg' } /> }
|
|
||||||
onClick={ () => this.toggleUploadDestination('aws') }
|
|
||||||
isSelected={ this.props.uploadDestinations.aws }
|
|
||||||
isStacked
|
|
||||||
isDisplayLarge />
|
|
||||||
<Tile
|
|
||||||
className="tile pf-u-mr-sm"
|
|
||||||
data-testid="upload-google"
|
|
||||||
title="Google Cloud Platform"
|
|
||||||
icon={ <img
|
|
||||||
className='provider-icon'
|
|
||||||
src={ '/apps/frontend-assets/partners-icons/google-cloud-short.svg' } /> }
|
|
||||||
onClick={ () => this.toggleUploadDestination('google') }
|
|
||||||
isSelected={ this.props.uploadDestinations.google }
|
|
||||||
isStacked
|
|
||||||
isDisplayLarge />
|
|
||||||
<Tile
|
|
||||||
className="tile"
|
|
||||||
data-testid="upload-azure"
|
|
||||||
title="Microsoft Azure"
|
|
||||||
icon={ <img
|
|
||||||
className='provider-icon'
|
|
||||||
src={ '/apps/frontend-assets/partners-icons/microsoft-azure-short.svg' } /> }
|
|
||||||
onClick={ () => this.toggleUploadDestination('azure') }
|
|
||||||
isSelected={ this.props.uploadDestinations.azure }
|
|
||||||
isStacked
|
|
||||||
isDisplayLarge />
|
|
||||||
</div>
|
|
||||||
</FormGroup>
|
|
||||||
</Form>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
release: state.pendingCompose.release,
|
|
||||||
uploadDestinations: state.pendingCompose.uploadDestinations,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
setRelease: i => dispatch(actions.setRelease(i)),
|
|
||||||
setUploadDestinations: d => dispatch(actions.setUploadDestinations(d)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
WizardStepImageOutput.propTypes = {
|
|
||||||
setRelease: PropTypes.func,
|
|
||||||
setUploadDestinations: PropTypes.func,
|
|
||||||
release: PropTypes.object,
|
|
||||||
uploadDestinations: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(WizardStepImageOutput);
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
.tiles {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile {
|
|
||||||
flex: 1 0 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-c-tile:focus {
|
|
||||||
--pf-c-tile__title--Color: var(--pf-c-tile__title--Color);
|
|
||||||
--pf-c-tile__icon--Color: var(---pf-global--Color--100);
|
|
||||||
--pf-c-tile--before--BorderWidth: var(--pf-global--BorderWidth--sm);
|
|
||||||
--pf-c-tile--before--BorderColor: var(--pf-global--BorderColor--100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pf-c-tile.pf-m-selected:focus {
|
|
||||||
--pf-c-tile__title--Color: var(--pf-c-tile--focus__title--Color);
|
|
||||||
--pf-c-tile__icon--Color: var(--pf-c-tile--focus__icon--Color);
|
|
||||||
--pf-c-tile--before--BorderWidth: var(--pf-c-tile--focus--before--BorderWidth);
|
|
||||||
--pf-c-tile--before--BorderColor: var(--pf-c-tile--focus--before--BorderColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.provider-icon {
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
}
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { Button, DualListSelector, Text, TextContent, Title } from '@patternfly/react-core';
|
|
||||||
|
|
||||||
import { actions } from '../../store/actions';
|
|
||||||
import api from '../../api.js';
|
|
||||||
|
|
||||||
import './WizardStepPackages.scss';
|
|
||||||
class WizardStepPackages extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.setPackagesSearchName = this.setPackagesSearchName.bind(this);
|
|
||||||
this.handlePackagesSearch = this.handlePackagesSearch.bind(this);
|
|
||||||
this.handlePackagesFilter = this.handlePackagesFilter.bind(this);
|
|
||||||
this.packageListChange = this.packageListChange.bind(this);
|
|
||||||
this.mapPackagesToComponent = this.mapPackagesToComponent.bind(this);
|
|
||||||
|
|
||||||
const comps = this.mapPackagesToComponent(this.props.selectedPackages);
|
|
||||||
this.state = {
|
|
||||||
packagesAvailableComponents: [],
|
|
||||||
packagesSelectedComponents: comps,
|
|
||||||
packagesFilteredComponents: comps,
|
|
||||||
packagesSearchName: '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
setPackagesSearchName(packagesSearchName) {
|
|
||||||
this.setState({ packagesSearchName });
|
|
||||||
}
|
|
||||||
|
|
||||||
mapPackagesToComponent(packages) {
|
|
||||||
return packages.map((pack) =>
|
|
||||||
<TextContent key={ pack }>
|
|
||||||
<span className="pf-c-dual-list-selector__item-text">{ pack.name }</span>
|
|
||||||
<small>{ pack.summary }</small>
|
|
||||||
</TextContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// this digs into the component properties to extract the package
|
|
||||||
mapComponentToPackage(component) {
|
|
||||||
return { name: component.props.children[0].props.children, summary: component.props.children[1].props.children };
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePackagesSearch() {
|
|
||||||
api.getPackages(this.props.release.distro, this.props.release.arch, this.state.packagesSearchName).then(response => {
|
|
||||||
const packageComponents = this.mapPackagesToComponent(response.data);
|
|
||||||
this.setState({
|
|
||||||
packagesAvailableComponents: packageComponents
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handlePackagesFilter(filter) {
|
|
||||||
const filteredPackages = this.state.packagesSelectedComponents.filter(component => {
|
|
||||||
return this.mapComponentToPackage(component).name.includes(filter);
|
|
||||||
});
|
|
||||||
this.setState({
|
|
||||||
packagesFilteredComponents: filteredPackages
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
packageListChange(newAvailablePackages, newChosenPackages) {
|
|
||||||
const chosenPkgs = newChosenPackages.map(component => this.mapComponentToPackage(component));
|
|
||||||
this.setState({
|
|
||||||
packagesAvailableComponents: newAvailablePackages,
|
|
||||||
packagesSelectedComponents: newChosenPackages,
|
|
||||||
packagesFilteredComponents: newChosenPackages,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.setSelectedPackages(chosenPkgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const availableOptionsActions = [
|
|
||||||
<Button
|
|
||||||
aria-label="Search button for available packages"
|
|
||||||
key="availableSearchButton"
|
|
||||||
data-testid="search-pkgs-button"
|
|
||||||
onClick={ this.handlePackagesSearch }>
|
|
||||||
Search
|
|
||||||
</Button>
|
|
||||||
];
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TextContent>
|
|
||||||
<Title headingLevel="h2" size="xl">Additional packages</Title>
|
|
||||||
<Text>Add optional additional packages to your image by searching available packages.</Text>
|
|
||||||
</TextContent>
|
|
||||||
<DualListSelector
|
|
||||||
className="pf-u-mt-sm"
|
|
||||||
isSearchable
|
|
||||||
availableOptionsActions={ availableOptionsActions }
|
|
||||||
availableOptions={ this.state.packagesAvailableComponents }
|
|
||||||
availableOptionsTitle={ 'Available packages' }
|
|
||||||
chosenOptions={ this.state.packagesFilteredComponents }
|
|
||||||
chosenOptionsTitle={ 'Chosen packages' }
|
|
||||||
addSelected={ this.packageListChange }
|
|
||||||
removeSelected={ this.packageListChange }
|
|
||||||
addAll={ this.packageListChange }
|
|
||||||
removeAll= { this.packageListChange }
|
|
||||||
onAvailableOptionsSearchInputChanged={ this.setPackagesSearchName }
|
|
||||||
onChosenOptionsSearchInputChanged={ this.handlePackagesFilter }
|
|
||||||
filterOption={ () => true }
|
|
||||||
id="basicSelectorWithSearch" />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
release: state.pendingCompose.release,
|
|
||||||
selectedPackages: state.pendingCompose.selectedPackages,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
setSelectedPackages: (p) => dispatch(actions.setSelectedPackages(p)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
WizardStepPackages.propTypes = {
|
|
||||||
release: PropTypes.object,
|
|
||||||
selectedPackages: PropTypes.array,
|
|
||||||
setSelectedPackages: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(WizardStepPackages);
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
.pf-c-dual-list-selector__menu {
|
|
||||||
--pf-c-dual-list-selector__menu--MinHeight: 17.5rem
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { Form, FormGroup, TextInput, Radio, Title } from '@patternfly/react-core';
|
|
||||||
|
|
||||||
import { actions } from '../../store/actions';
|
|
||||||
|
|
||||||
class WizardStepRegistration extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidMount() {
|
|
||||||
let user = await insights.chrome.auth.getUser();
|
|
||||||
this.props.setSubscription(Object.assign(this.props.subscription, { organization: user.identity.internal.org_id }));
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Form>
|
|
||||||
<Title headingLevel="h2" size="xl">Registration</Title>
|
|
||||||
<FormGroup isRequired label="Register the system">
|
|
||||||
<Radio name="subscribe-now-radio" isChecked={ this.props.subscribeNow } id="subscribe-now-radio"
|
|
||||||
label="Embed an activation key and register systems on first boot"
|
|
||||||
onChange={ () => this.props.setSubscribeNow(true) }
|
|
||||||
data-testid="register-now-radio-button" />
|
|
||||||
<Radio name="subscribe-later-radio" isChecked={ !this.props.subscribeNow }
|
|
||||||
label="Register the system later" id="subscribe-later-radio"
|
|
||||||
onChange={ () => this.props.setSubscribeNow(false) }
|
|
||||||
data-testid="register-later-radio-button" />
|
|
||||||
</FormGroup>
|
|
||||||
{ this.props.subscribeNow &&
|
|
||||||
<>
|
|
||||||
<FormGroup label="Organization ID" fieldId="subscription-organization">
|
|
||||||
<TextInput isDisabled value={ this.props.subscription.organization || '' } type="text"
|
|
||||||
id="subscription-organization" aria-label="Subscription organization ID"
|
|
||||||
data-testid="organization-id" />
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup isRequired label="Activation key" fieldId="subscription-activation"
|
|
||||||
helperTextInvalid={ 'A value is required' }
|
|
||||||
validated={ !this.props.isValidSubscription && this.props.subscription.activationKey !== null ? 'error' : 'default' }>
|
|
||||||
<TextInput
|
|
||||||
value={ this.props.subscription.activationKey || '' }
|
|
||||||
type="password"
|
|
||||||
data-testid="subscription-activation"
|
|
||||||
id="subscription-activation"
|
|
||||||
aria-label="Subscription activation key"
|
|
||||||
onChange={ activationKey => this.props.setSubscription(Object.assign(this.props.subscription, { activationKey })) }
|
|
||||||
validated={ !this.props.isValidSubscription && this.props.subscription.activationKey !== null ? 'error' : 'default' }
|
|
||||||
isRequired />
|
|
||||||
</FormGroup>
|
|
||||||
</> }
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
subscription: state.pendingCompose.subscription,
|
|
||||||
subscribeNow: state.pendingCompose.subscribeNow,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
setSubscription: s => dispatch(actions.setSubscription(s)),
|
|
||||||
setSubscribeNow: s => dispatch(actions.setSubscribeNow(s)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
WizardStepRegistration.propTypes = {
|
|
||||||
setSubscription: PropTypes.func,
|
|
||||||
setSubscribeNow: PropTypes.func,
|
|
||||||
subscription: PropTypes.object,
|
|
||||||
subscribeNow: PropTypes.bool,
|
|
||||||
isValidSubscription: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(WizardStepRegistration);
|
|
||||||
|
|
@ -1,162 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import {
|
|
||||||
Alert,
|
|
||||||
Text, TextVariants, TextContent, TextList, TextListVariants, TextListItem, TextListItemVariants,
|
|
||||||
Title
|
|
||||||
} from '@patternfly/react-core';
|
|
||||||
import { ExclamationCircleIcon } from '@patternfly/react-icons';
|
|
||||||
|
|
||||||
import './WizardStepReview.scss';
|
|
||||||
|
|
||||||
class WizardStepReview extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const releaseLabels = {
|
|
||||||
'rhel-84': 'Red Hat Enterprise Linux (RHEL) 8',
|
|
||||||
'centos-8': 'CentOS Stream 8'
|
|
||||||
};
|
|
||||||
|
|
||||||
const awsReview = (
|
|
||||||
<>
|
|
||||||
<Text id="destination-header">Amazon Web Services</Text>
|
|
||||||
<TextList component={ TextListVariants.dl } data-testid='review-image-upload-aws'>
|
|
||||||
<TextListItem component={ TextListItemVariants.dt }>Account ID</TextListItem>
|
|
||||||
{this.props.uploadAWSErrors['aws-account-id'] ? (
|
|
||||||
<TextListItem component={ TextListItemVariants.dd }>
|
|
||||||
<ExclamationCircleIcon className="error" /> { this.props.uploadAWSErrors['aws-account-id'].value }
|
|
||||||
</TextListItem>
|
|
||||||
) : (
|
|
||||||
<TextListItem component={ TextListItemVariants.dd }>{this.props.uploadAWS.shareWithAccounts[0]}</TextListItem>
|
|
||||||
)}
|
|
||||||
</TextList>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const googleReview = (
|
|
||||||
<>
|
|
||||||
<Text id="destination-header">Google Cloud Platform</Text>
|
|
||||||
<TextList component={ TextListVariants.dl } data-testid='review-image-upload-google'>
|
|
||||||
{this.props.uploadGoogle.accountType === 'googleAccount' && (
|
|
||||||
<>
|
|
||||||
<TextListItem component={ TextListItemVariants.dt }>Google account</TextListItem>
|
|
||||||
<TextListItem component={ TextListItemVariants.dd }>{this.props.uploadGoogle.shareWithAccounts[0] ?
|
|
||||||
this.props.uploadGoogle.shareWithAccounts[0].user || '' :
|
|
||||||
''}
|
|
||||||
</TextListItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{this.props.uploadGoogle.accountType === 'serviceAccount' && (
|
|
||||||
<>
|
|
||||||
<TextListItem component={ TextListItemVariants.dt }>Service account</TextListItem>
|
|
||||||
<TextListItem component={ TextListItemVariants.dd }>{this.props.uploadGoogle.shareWithAccounts[0] ?
|
|
||||||
this.props.uploadGoogle.shareWithAccounts[0].serviceAccount || '' :
|
|
||||||
''}
|
|
||||||
</TextListItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{this.props.uploadGoogle.accountType === 'googleGroup' && (
|
|
||||||
<>
|
|
||||||
<TextListItem component={ TextListItemVariants.dt }>Google group</TextListItem>
|
|
||||||
<TextListItem component={ TextListItemVariants.dd }>{this.props.uploadGoogle.shareWithAccounts[0] ?
|
|
||||||
this.props.uploadGoogle.shareWithAccounts[0].group || '' :
|
|
||||||
''}
|
|
||||||
</TextListItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{this.props.uploadGoogle.accountType === 'domain' && (
|
|
||||||
<>
|
|
||||||
<TextListItem component={ TextListItemVariants.dt }>Domain</TextListItem>
|
|
||||||
<TextListItem component={ TextListItemVariants.dd }>{this.props.uploadGoogle.shareWithAccounts[0] ?
|
|
||||||
this.props.uploadGoogle.shareWithAccounts[0].domain || '' :
|
|
||||||
''}
|
|
||||||
</TextListItem>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</TextList>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
let subscriptionReview = <TextListItem component={ TextListItemVariants.dd }>Register the system later</TextListItem>;
|
|
||||||
if (this.props.subscribeNow) {
|
|
||||||
subscriptionReview = (<>
|
|
||||||
<TextListItem component={ TextListItemVariants.dd }>Register the system on first boot</TextListItem>
|
|
||||||
<TextListItem component={ TextListItemVariants.dt }>Activation key</TextListItem>
|
|
||||||
{ !this.props.isValidSubscription || !this.props.subscription.activationKey ? (
|
|
||||||
<TextListItem component={ TextListItemVariants.dd }>
|
|
||||||
<ExclamationCircleIcon className="error" /> { 'A value is required' }
|
|
||||||
</TextListItem>
|
|
||||||
) : (
|
|
||||||
<TextListItem component={ TextListItemVariants.dd } type="password">
|
|
||||||
{'*'.repeat(this.props.subscription.activationKey.length)}
|
|
||||||
</TextListItem>
|
|
||||||
)}
|
|
||||||
</>);
|
|
||||||
}
|
|
||||||
|
|
||||||
const registrationReview = (
|
|
||||||
<>
|
|
||||||
<Text component={ TextVariants.h3 }>Registration</Text>
|
|
||||||
<TextList component={ TextListVariants.dl } data-testid='review-image-registration'>
|
|
||||||
<TextListItem component={ TextListItemVariants.dt }>Subscription</TextListItem>
|
|
||||||
{ subscriptionReview }
|
|
||||||
</TextList>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{ (Object.keys(this.props.uploadAWSErrors).length > 0 ||
|
|
||||||
!this.props.isValidSubscription) &&
|
|
||||||
<Alert variant="danger" className="pf-u-mb-xl" isInline title="Required information is missing" /> }
|
|
||||||
<Title headingLevel="h2" size="xl">Review</Title>
|
|
||||||
<TextContent>
|
|
||||||
<Text component={ TextVariants.small }>
|
|
||||||
Review the information and click the Create button
|
|
||||||
to create your image using the following criteria.
|
|
||||||
</Text>
|
|
||||||
<Text component={ TextVariants.h3 }>Image output</Text>
|
|
||||||
<TextList component={ TextListVariants.dl } data-testid='review-image-output'>
|
|
||||||
<TextListItem component={ TextListItemVariants.dt }>Release</TextListItem>
|
|
||||||
<TextListItem component={ TextListItemVariants.dd }>{releaseLabels[this.props.release.distro]}</TextListItem>
|
|
||||||
</TextList>
|
|
||||||
<Text component={ TextVariants.h3 }>Target environment</Text>
|
|
||||||
{this.props.uploadDestinations.aws && awsReview }
|
|
||||||
{this.props.uploadDestinations.google && googleReview }
|
|
||||||
{this.props.release.distro === 'rhel-84' && registrationReview }
|
|
||||||
</TextContent>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
release: state.pendingCompose.release,
|
|
||||||
uploadDestinations: state.pendingCompose.uploadDestinations,
|
|
||||||
uploadAWS: state.pendingCompose.uploadAWS,
|
|
||||||
uploadAzure: state.pendingCompose.uploadAzure,
|
|
||||||
uploadGoogle: state.pendingCompose.uploadGoogle,
|
|
||||||
subscribeNow: state.pendingCompose.subscribeNow,
|
|
||||||
subscription: state.pendingCompose.subscription,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
WizardStepReview.propTypes = {
|
|
||||||
release: PropTypes.object,
|
|
||||||
uploadAWS: PropTypes.object,
|
|
||||||
uploadGoogle: PropTypes.object,
|
|
||||||
uploadDestinations: PropTypes.object,
|
|
||||||
uploadAzure: PropTypes.object,
|
|
||||||
subscription: PropTypes.object,
|
|
||||||
subscribeNow: PropTypes.bool,
|
|
||||||
uploadAWSErrors: PropTypes.object,
|
|
||||||
isValidSubscription: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(WizardStepReview);
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
.error {
|
|
||||||
color: var(--pf-global--danger-color--100);
|
|
||||||
}
|
|
||||||
// Increasing margins for h3 for better spacing and readability
|
|
||||||
.textcontent-review h3 {
|
|
||||||
margin-top: var(--pf-global--spacer--xl);
|
|
||||||
}
|
|
||||||
// Decreasing gap between dl items for better spacing and readability
|
|
||||||
// Also setting a first column width to 25% instead of auto,
|
|
||||||
// to guarantee the same width for each section.
|
|
||||||
@media screen and (min-width: 576px) {
|
|
||||||
.textcontent-review dl {
|
|
||||||
grid-template: 1fr / 25% 1fr;
|
|
||||||
grid-gap: var(--pf-global--spacer--sm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#destination-header {
|
|
||||||
font-size: 18px;
|
|
||||||
margin-bottom: var(--pf-global--spacer--sm);
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { actions } from '../../store/actions';
|
|
||||||
|
|
||||||
import { Form, FormGroup, TextInput, Title } from '@patternfly/react-core';
|
|
||||||
|
|
||||||
class WizardStepUploadAWS extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Form>
|
|
||||||
<Title headingLevel="h2" size="xl">Target Environment - Amazon Web Services</Title>
|
|
||||||
<p>
|
|
||||||
Your image will be uploaded to a temporary account on Amazon Web Services. <br />
|
|
||||||
The image will be shared with the account you provide below. <br />
|
|
||||||
Within the next 14 days you will need to copy the shared image to your own account.
|
|
||||||
After 14 days it will be unavailable and will have to be regenerated.
|
|
||||||
</p>
|
|
||||||
<FormGroup isRequired label="AWS account ID" fieldId="aws-account-id"
|
|
||||||
helperTextInvalid={ (this.props.errors['aws-account-id'] && this.props.errors['aws-account-id'].value) || '' }
|
|
||||||
validated={ (this.props.errors['aws-account-id'] && 'error') || 'default' }>
|
|
||||||
<TextInput value={ this.props.uploadAWS.shareWithAccounts || '' }
|
|
||||||
type="text" aria-label="AWS account ID" id="aws-account-id"
|
|
||||||
data-testid="aws-account-id" isRequired
|
|
||||||
onChange={ value => this.props.setUploadAWS({ shareWithAccounts: [ value ]}) } />
|
|
||||||
</FormGroup>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
uploadAWS: state.pendingCompose.uploadAWS,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
setUploadAWS: u => dispatch(actions.setUploadAWS(u)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
WizardStepUploadAWS.propTypes = {
|
|
||||||
setUploadAWS: PropTypes.func,
|
|
||||||
uploadAWS: PropTypes.object,
|
|
||||||
errors: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(WizardStepUploadAWS);
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { actions } from '../../store/actions';
|
|
||||||
|
|
||||||
import { Button, Form, FormGroup, Text, TextContent, TextInput, Title } from '@patternfly/react-core';
|
|
||||||
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
|
|
||||||
|
|
||||||
import './WizardStepUploadAzure.scss';
|
|
||||||
|
|
||||||
class WizardStepUploadAzure extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TextContent className="textcontent-azure">
|
|
||||||
<Title headingLevel="h2">Target Environment - Microsoft Azure</Title>
|
|
||||||
<Text>
|
|
||||||
Image Builder will send an image to an authorized Azure account.
|
|
||||||
</Text>
|
|
||||||
<Title headingLevel="h3">OAuth permissions</Title>
|
|
||||||
<Text>
|
|
||||||
To authorize Image Builder to push images to Microsoft Azure, the account owner
|
|
||||||
must configure Image Builder as an authorized application and give it the role of
|
|
||||||
"Contributor" to at least one resource group.<br />
|
|
||||||
</Text>
|
|
||||||
<small>
|
|
||||||
<Button
|
|
||||||
component="a"
|
|
||||||
target="_blank"
|
|
||||||
variant="link"
|
|
||||||
icon={ <ExternalLinkAltIcon /> }
|
|
||||||
iconPosition="right"
|
|
||||||
isInline
|
|
||||||
href="https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow">
|
|
||||||
Learn more about OAuth 2.0
|
|
||||||
</Button>
|
|
||||||
</small>
|
|
||||||
<a href="https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=b94bb246-b02c-4985-9c22-d44e66f657f4
|
|
||||||
&scope=openid&response_type=code&response_mode=form_post
|
|
||||||
&redirect_uri=https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient" target="_blank" rel="noopener noreferrer">
|
|
||||||
Authorize Image Builder on Azure <ExternalLinkAltIcon />
|
|
||||||
</a>
|
|
||||||
</TextContent>
|
|
||||||
<Title headingLevel="h3">Destination</Title>
|
|
||||||
<Text>
|
|
||||||
Your image will be uploaded to the resource group in the subscription you specify.
|
|
||||||
</Text>
|
|
||||||
<br />
|
|
||||||
<Form isWidthLimited>
|
|
||||||
<FormGroup isRequired label="Tenant ID" fieldId="azure-tenant-id"
|
|
||||||
helperTextInvalid={ (this.props.errors['azure-tenant-id'] && this.props.errors['azure-tenant-id'].value) || '' }
|
|
||||||
validated={ (this.props.errors['azure-tenant-id'] && 'error') || 'default' }>
|
|
||||||
<TextInput value={ this.props.uploadAzure.tenantId || '' }
|
|
||||||
type="text" aria-label="Azure tenant-id" id="azure-tenant-id"
|
|
||||||
data-testid="azure-tenant-id" isRequired
|
|
||||||
onChange={ value =>
|
|
||||||
this.props.setUploadAzure(Object.assign(this.props.uploadAzure, { tenantId: value })) } />
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup isRequired label="Subscription ID" fieldId="azure-subscription-id"
|
|
||||||
helperTextInvalid={ (this.props.errors['azure-subscription-id'] &&
|
|
||||||
this.props.errors['azure-subscription-id'].value) || '' }
|
|
||||||
validated={ (this.props.errors['azure-subscription-id'] && 'error') || 'default' }>
|
|
||||||
<TextInput value={ this.props.uploadAzure.subscriptionId || '' }
|
|
||||||
type="text" aria-label="Azure subscription-id" id="azure-subscription-id"
|
|
||||||
data-testid="azure-subscription-id" isRequired
|
|
||||||
onChange={ value =>
|
|
||||||
this.props.setUploadAzure(Object.assign(this.props.uploadAzure, { subscriptionId: value })) } />
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup isRequired label="Resource group" fieldId="azure-resource-group"
|
|
||||||
helperTextInvalid={ (this.props.errors['azure-resource-group'] &&
|
|
||||||
this.props.errors['azure-resource-group'].value) || '' }
|
|
||||||
validated={ (this.props.errors['azure-resource-group'] && 'error') || 'default' }>
|
|
||||||
<TextInput value={ this.props.uploadAzure.resourceGroup || '' }
|
|
||||||
type="text" aria-label="Azure resource group" id="azure-resource-group"
|
|
||||||
data-testid="azure-resource-group" isRequired
|
|
||||||
onChange={ value =>
|
|
||||||
this.props.setUploadAzure(Object.assign(this.props.uploadAzure, { resourceGroup: value })) } />
|
|
||||||
</FormGroup>
|
|
||||||
</Form>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
uploadAzure: state.pendingCompose.uploadAzure,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
setUploadAzure: u => dispatch(actions.setUploadAzure(u)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
WizardStepUploadAzure.propTypes = {
|
|
||||||
setUploadAzure: PropTypes.func,
|
|
||||||
uploadAzure: PropTypes.object,
|
|
||||||
errors: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(WizardStepUploadAzure);
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
.textcontent-azure {
|
|
||||||
margin-bottom: var(--pf-global--spacer--lg);
|
|
||||||
h3, h4 {
|
|
||||||
margin-top: var(--pf-global--spacer--sm);
|
|
||||||
margin-bottom: var(--pf-global--spacer--xs);
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,166 +0,0 @@
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { actions } from '../../store/actions';
|
|
||||||
|
|
||||||
import { Form, FormGroup, TextList, TextListItem, Popover, Radio, TextContent, Text, TextInput, Title } from '@patternfly/react-core';
|
|
||||||
import { HelpIcon } from '@patternfly/react-icons';
|
|
||||||
import './WizardStepUploadGoogle.scss';
|
|
||||||
|
|
||||||
const accountTypePopover = (
|
|
||||||
<Popover
|
|
||||||
hasAutoWidth
|
|
||||||
maxWidth='35rem'
|
|
||||||
headerContent={ 'Valid account types' }
|
|
||||||
bodyContent={ <TextContent>
|
|
||||||
<Text>The following account types can have an image shared with them:</Text>
|
|
||||||
<TextList>
|
|
||||||
<TextListItem>
|
|
||||||
<strong>Google account:</strong> A Google account represents a developer, an administrator,
|
|
||||||
or any other person who interacts with Google Cloud. e.g., <em>`alice@gmail.com`</em>.
|
|
||||||
</TextListItem>
|
|
||||||
<TextListItem>
|
|
||||||
<strong>Service account:</strong> A service account is an account for an application instead
|
|
||||||
of an individual end user. e.g., <em>`myapp@appspot.gserviceaccount.com`</em>.
|
|
||||||
</TextListItem>
|
|
||||||
<TextListItem>
|
|
||||||
<strong>Google group:</strong> A Google group is a named collection of Google accounts and
|
|
||||||
and service accounts. e.g., <em>`admins@example.com`</em>.
|
|
||||||
</TextListItem>
|
|
||||||
<TextListItem>
|
|
||||||
<strong>Google workspace domain/Cloud identity domain:</strong> A Google workspace or cloud identity
|
|
||||||
domain represents a virtual group of all the Google accounts in an organization. These domains
|
|
||||||
represent your organization's internet domain name. e.g., <em>`mycompany.com`</em>.
|
|
||||||
</TextListItem>
|
|
||||||
</TextList>
|
|
||||||
</TextContent> }>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label="Account info"
|
|
||||||
aria-describedby="google-account-type"
|
|
||||||
className="pf-c-form__group-label-help">
|
|
||||||
<HelpIcon />
|
|
||||||
</button>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
|
|
||||||
class WizardStepUploadGoogle extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TextContent className="textcontent-google">
|
|
||||||
<Title headingLevel="h2" size="xl">Target Environment - Google Cloud Platform</Title>
|
|
||||||
<Text>
|
|
||||||
Your image will be uploaded to Google Cloud Platform and shared with the email you provide below. <br />
|
|
||||||
The image should be copied to your account within 14 days.
|
|
||||||
</Text>
|
|
||||||
</TextContent>
|
|
||||||
<Form isWidthLimited>
|
|
||||||
<FormGroup isRequired label="Type" labelIcon={ accountTypePopover } fieldId="google-account-type">
|
|
||||||
<Radio
|
|
||||||
onChange={ () => this.props.setUploadGoogle({ accountType: 'googleAccount', shareWithAccounts: [{ user: null }]}) }
|
|
||||||
isChecked={ this.props.uploadGoogle.accountType === 'googleAccount' }
|
|
||||||
label="Google account"
|
|
||||||
id="radio-google-account"
|
|
||||||
value="googleAccount" />
|
|
||||||
<Radio
|
|
||||||
onChange={ () =>
|
|
||||||
this.props.setUploadGoogle({ accountType: 'serviceAccount', shareWithAccounts: [{ serviceAccount: null }]}) }
|
|
||||||
isChecked={ this.props.uploadGoogle.accountType === 'serviceAccount' }
|
|
||||||
label="Service account"
|
|
||||||
id="radio-service-account"
|
|
||||||
value="serviceAccount" />
|
|
||||||
<Radio
|
|
||||||
onChange={ () => this.props.setUploadGoogle({ accountType: 'googleGroup', shareWithAccounts: [{ group: null }]}) }
|
|
||||||
isChecked={ this.props.uploadGoogle.accountType === 'googleGroup' }
|
|
||||||
label="Google group"
|
|
||||||
id="radio-google-group"
|
|
||||||
value="googleGroup" />
|
|
||||||
<Radio
|
|
||||||
onChange={ () => this.props.setUploadGoogle({ accountType: 'domain', shareWithAccounts: [{ domain: null }]}) }
|
|
||||||
isChecked={ this.props.uploadGoogle.accountType === 'domain' }
|
|
||||||
label="Google Workspace Domain or Cloud Identity Domain"
|
|
||||||
id="radio-domain"
|
|
||||||
value="domain" />
|
|
||||||
</FormGroup>
|
|
||||||
{this.props.uploadGoogle.accountType === 'googleAccount' && (
|
|
||||||
<FormGroup isRequired label="Email address" fieldId="user">
|
|
||||||
<TextInput
|
|
||||||
value={ this.props.uploadGoogle.shareWithAccounts[0] ?
|
|
||||||
this.props.uploadGoogle.shareWithAccounts[0].user || '' :
|
|
||||||
'' }
|
|
||||||
type="text" aria-label="Google email address" id="input-google-user"
|
|
||||||
data-testid="input-google-user" isRequired
|
|
||||||
onChange={ value => this.props.setUploadGoogle(
|
|
||||||
{ accountType: 'googleAccount', shareWithAccounts: [{ user: value }]}
|
|
||||||
) } />
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
{this.props.uploadGoogle.accountType === 'serviceAccount' && (
|
|
||||||
<FormGroup isRequired label="Email address" fieldId="service-account">
|
|
||||||
<TextInput
|
|
||||||
value={ this.props.uploadGoogle.shareWithAccounts[0] ?
|
|
||||||
this.props.uploadGoogle.shareWithAccounts[0].serviceAccount || '' :
|
|
||||||
'' }
|
|
||||||
type="text" aria-label="Google email address" id="input-google-service-account"
|
|
||||||
data-testid="input-google-service-account" isRequired
|
|
||||||
onChange={ value => this.props.setUploadGoogle(
|
|
||||||
{ accountType: 'serviceAccount', shareWithAccounts: [{ serviceAccount: value }]}
|
|
||||||
) } />
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
{this.props.uploadGoogle.accountType === 'googleGroup' && (
|
|
||||||
<FormGroup isRequired label="Email address" fieldId="group">
|
|
||||||
<TextInput
|
|
||||||
value={ this.props.uploadGoogle.shareWithAccounts[0] ?
|
|
||||||
this.props.uploadGoogle.shareWithAccounts[0].group || '' :
|
|
||||||
'' }
|
|
||||||
type="text" aria-label="Google email address" id="input-google-group"
|
|
||||||
data-testid="input-google-group" isRequired
|
|
||||||
onChange={ value => this.props.setUploadGoogle(
|
|
||||||
{ accountType: 'googleGroup', shareWithAccounts: [{ group: value }]}
|
|
||||||
) } />
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
{this.props.uploadGoogle.accountType === 'domain' && (
|
|
||||||
<FormGroup isRequired label="Domain" fieldId="domain">
|
|
||||||
<TextInput
|
|
||||||
value={ this.props.uploadGoogle.shareWithAccounts[0] ?
|
|
||||||
this.props.uploadGoogle.shareWithAccounts[0].domain || '' :
|
|
||||||
'' }
|
|
||||||
type="text" aria-label="Google domain" id="input-google-domain"
|
|
||||||
data-testid="input-google-domain" isRequired
|
|
||||||
onChange={ value => this.props.setUploadGoogle(
|
|
||||||
{ accountType: 'domain', shareWithAccounts: [{ domain: value }]}
|
|
||||||
) } />
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
uploadGoogle: state.pendingCompose.uploadGoogle,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
setUploadGoogle: u => dispatch(actions.setUploadGoogle(u)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
WizardStepUploadGoogle.propTypes = {
|
|
||||||
setUploadGoogle: PropTypes.func,
|
|
||||||
uploadGoogle: PropTypes.object,
|
|
||||||
errors: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(WizardStepUploadGoogle);
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
.pf-c-radio {
|
|
||||||
--pf-c-radio__label--LineHeight: 32px;
|
|
||||||
--pf-c-radio__input--Height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textcontent-google {
|
|
||||||
margin-bottom: var(--pf-global--spacer--lg);
|
|
||||||
}
|
|
||||||
|
|
@ -558,10 +558,11 @@ describe('Click through all steps', () => {
|
||||||
const composeImage = jest
|
const composeImage = jest
|
||||||
.spyOn(api, 'composeImage')
|
.spyOn(api, 'composeImage')
|
||||||
.mockImplementation(body => {
|
.mockImplementation(body => {
|
||||||
|
console.log(body, 'huuuh!');
|
||||||
let id;
|
let id;
|
||||||
if (body.image_requests[0].upload_request.type === 'aws') {
|
if (body.image_requests[0].upload_request.type === 'aws') {
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
distribution: 'rhel-84',
|
distribution: 'rhel-8',
|
||||||
image_requests: [{
|
image_requests: [{
|
||||||
architecture: 'x86_64',
|
architecture: 'x86_64',
|
||||||
image_type: 'ami',
|
image_type: 'ami',
|
||||||
|
|
@ -586,7 +587,7 @@ describe('Click through all steps', () => {
|
||||||
id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f56';
|
id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f56';
|
||||||
} else if (body.image_requests[0].upload_request.type === 'gcp') {
|
} else if (body.image_requests[0].upload_request.type === 'gcp') {
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
distribution: 'rhel-84',
|
distribution: 'rhel-8',
|
||||||
image_requests: [{
|
image_requests: [{
|
||||||
architecture: 'x86_64',
|
architecture: 'x86_64',
|
||||||
image_type: 'vhd',
|
image_type: 'vhd',
|
||||||
|
|
@ -611,7 +612,7 @@ describe('Click through all steps', () => {
|
||||||
id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f57';
|
id = 'edbae1c2-62bc-42c1-ae0c-3110ab718f57';
|
||||||
} else if (body.image_requests[0].upload_request.type === 'azure') {
|
} else if (body.image_requests[0].upload_request.type === 'azure') {
|
||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
distribution: 'rhel-84',
|
distribution: 'rhel-8',
|
||||||
image_requests: [{
|
image_requests: [{
|
||||||
architecture: 'x86_64',
|
architecture: 'x86_64',
|
||||||
image_type: 'vhd',
|
image_type: 'vhd',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue