ShareImageModal: Add ShareImageModal to clone (share) AWS composes
This commit is contained in:
parent
5c37e3b45b
commit
a42a6a220f
4 changed files with 294 additions and 1 deletions
|
|
@ -140,6 +140,17 @@ const ImagesTable = () => {
|
|||
},
|
||||
];
|
||||
|
||||
const awsActions = (compose) => [
|
||||
{
|
||||
title: 'Share to new region',
|
||||
onClick: () =>
|
||||
navigate(resolveRelPath(`share`), {
|
||||
state: { composeId: compose.id },
|
||||
}),
|
||||
},
|
||||
...actions(compose),
|
||||
];
|
||||
|
||||
// the state.page is not an index so must be reduced by 1 get the starting index
|
||||
const itemsStartInclusive = (page - 1) * perPage;
|
||||
const itemsEndExclusive = itemsStartInclusive + perPage;
|
||||
|
|
@ -253,7 +264,12 @@ const ImagesTable = () => {
|
|||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<ActionsColumn items={actions(compose)} />
|
||||
{compose.request.image_requests[0].upload_request
|
||||
.type === 'aws' ? (
|
||||
<ActionsColumn items={awsActions(compose)} />
|
||||
) : (
|
||||
<ActionsColumn items={actions(compose)} />
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
<Tr isExpanded={isExpanded(compose)}>
|
||||
|
|
|
|||
228
src/Components/ShareImageModal/RegionsSelect.js
Normal file
228
src/Components/ShareImageModal/RegionsSelect.js
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux';
|
||||
import {
|
||||
ActionGroup,
|
||||
Button,
|
||||
Form,
|
||||
FormGroup,
|
||||
Popover,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
} from '@patternfly/react-core';
|
||||
import { ExclamationCircleIcon, HelpIcon } from '@patternfly/react-icons';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { AWS_REGIONS } from '../../constants';
|
||||
import { selectClonesById, selectComposeById } from '../../store/composesSlice';
|
||||
import api from '../../api';
|
||||
import { cloneAdded } from '../../store/clonesSlice';
|
||||
import { resolveRelPath } from '../../Utilities/path';
|
||||
|
||||
export const selectRegionsToDisable = createSelector(
|
||||
[selectComposeById, selectClonesById],
|
||||
(compose, clones) => {
|
||||
let regions = new Set();
|
||||
regions.add(compose.region);
|
||||
clones.map((clone) => {
|
||||
clone.region &&
|
||||
clone.share_with_accounts[0] === compose.share_with_accounts[0] &&
|
||||
clone.status !== 'failure' &&
|
||||
regions.add(clone.region);
|
||||
});
|
||||
|
||||
return regions;
|
||||
}
|
||||
);
|
||||
|
||||
const prepareRegions = (regionsToDisable) => {
|
||||
const regions = AWS_REGIONS.map((region) => ({
|
||||
...region,
|
||||
disabled: regionsToDisable.has(region.value),
|
||||
}));
|
||||
|
||||
return regions;
|
||||
};
|
||||
|
||||
const RegionsSelect = ({
|
||||
composeId,
|
||||
handleClose,
|
||||
handleToggle,
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [selected, setSelected] = useState([]);
|
||||
const titleId = 'Clone this image';
|
||||
const [validated, setValidated] = useState('default');
|
||||
const [helperTextInvalid] = useState(
|
||||
'Select at least one region to share to.'
|
||||
);
|
||||
|
||||
const compose = useSelector((state) => selectComposeById(state, composeId));
|
||||
|
||||
const regionsToDisable = useSelector((state) =>
|
||||
selectRegionsToDisable(state, composeId)
|
||||
);
|
||||
const [options] = useState(prepareRegions(regionsToDisable));
|
||||
|
||||
const handleSelect = (event, selection) => {
|
||||
let nextSelected;
|
||||
if (selected.includes(selection)) {
|
||||
nextSelected = selected.filter((region) => region !== selection);
|
||||
setSelected(nextSelected);
|
||||
} else {
|
||||
nextSelected = [...selected, selection];
|
||||
setSelected(nextSelected);
|
||||
}
|
||||
nextSelected.length === 0 ? setValidated('error') : setValidated('default');
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setSelected([]);
|
||||
setIsOpen(false);
|
||||
setValidated('error');
|
||||
};
|
||||
|
||||
const generateRequests = () => {
|
||||
const requests = selected.map((region) => {
|
||||
return {
|
||||
region: region,
|
||||
share_with_accounts: [compose.share_with_accounts[0]],
|
||||
};
|
||||
});
|
||||
return requests;
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
setIsSaving(true);
|
||||
const requests = generateRequests();
|
||||
Promise.all(
|
||||
requests.map((request) =>
|
||||
api.cloneImage(composeId, request).then((response) => {
|
||||
dispatch(
|
||||
cloneAdded({
|
||||
clone: {
|
||||
...response,
|
||||
request,
|
||||
image_status: { status: 'pending' },
|
||||
},
|
||||
parent: composeId,
|
||||
})
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
navigate(resolveRelPath(''));
|
||||
dispatch(
|
||||
addNotification({
|
||||
variant: 'success',
|
||||
title: 'Your image is being shared',
|
||||
})
|
||||
);
|
||||
|
||||
setIsSaving(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
navigate(resolveRelPath(''));
|
||||
dispatch(
|
||||
addNotification({
|
||||
variant: 'danger',
|
||||
title: 'Your image could not be created',
|
||||
description: `Status code ${err.response.status}: ${err.response.statusText}`,
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<span id={titleId} hidden>
|
||||
Select a region
|
||||
</span>
|
||||
<FormGroup
|
||||
label="Select region"
|
||||
isRequired
|
||||
validated={validated}
|
||||
helperTextInvalid={helperTextInvalid}
|
||||
helperTextInvalidIcon={<ExclamationCircleIcon />}
|
||||
labelIcon={
|
||||
<Popover
|
||||
headerContent={<div>Sharing images to other regions</div>}
|
||||
bodyContent={
|
||||
<div>
|
||||
Your image will be built, uploaded to AWS, and shared to the
|
||||
regions you select. The shared image will expire within 14 days.
|
||||
To permanently access the image, copy the image, which will be
|
||||
shared to your account by Red Hat, to your own AWS account.
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="More info for name field"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
aria-describedby="simple-form-name-01"
|
||||
className="pf-c-form__group-label-help"
|
||||
>
|
||||
<HelpIcon noVerticalAlign />
|
||||
</button>
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<Select
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
typeAheadAriaLabel="Select a region"
|
||||
onToggle={handleToggle}
|
||||
onSelect={handleSelect}
|
||||
onClear={handleClear}
|
||||
selections={selected}
|
||||
isOpen={isOpen}
|
||||
aria-labelledby={titleId}
|
||||
placeholderText="Select a region"
|
||||
menuAppendTo="parent"
|
||||
validated={validated}
|
||||
maxHeight="25rem"
|
||||
>
|
||||
{options.map((option, index) => (
|
||||
<SelectOption
|
||||
isDisabled={option.disabled}
|
||||
key={index}
|
||||
value={option.value}
|
||||
{...(option.description && { description: option.description })}
|
||||
/>
|
||||
))}
|
||||
</Select>
|
||||
</FormGroup>
|
||||
<ActionGroup>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
variant="primary"
|
||||
key="share"
|
||||
isDisabled={selected.length === 0 || isSaving}
|
||||
isLoading={isSaving}
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
<Button variant="link" onClick={handleClose} key="cancel">
|
||||
Cancel
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
RegionsSelect.propTypes = {
|
||||
composeId: PropTypes.string,
|
||||
handleClose: PropTypes.func,
|
||||
handleToggle: PropTypes.func,
|
||||
isOpen: PropTypes.bool,
|
||||
setIsOpen: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default RegionsSelect;
|
||||
47
src/Components/ShareImageModal/ShareImageModal.js
Normal file
47
src/Components/ShareImageModal/ShareImageModal.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Modal } from '@patternfly/react-core';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import RegionsSelect from './RegionsSelect';
|
||||
import { resolveRelPath } from '../../Utilities/path';
|
||||
|
||||
const ShareToRegionsModal = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const handleClose = () => navigate(resolveRelPath(''));
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const composeId = location?.state?.composeId;
|
||||
|
||||
const handleToggle = (isOpen) => setIsOpen(isOpen);
|
||||
|
||||
const handleEscapePress = () => {
|
||||
if (isOpen) {
|
||||
handleToggle(isOpen);
|
||||
} else {
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={true}
|
||||
variant="small"
|
||||
aria-label="Share to new region"
|
||||
onClose={handleClose}
|
||||
title="Share to new region"
|
||||
description="Configure new regions for this image that will run on your AWS. All the
|
||||
regions will launch with the same configuration."
|
||||
onEscapePress={handleEscapePress}
|
||||
>
|
||||
<RegionsSelect
|
||||
composeId={composeId}
|
||||
handleClose={handleClose}
|
||||
handleToggle={handleToggle}
|
||||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShareToRegionsModal;
|
||||
Loading…
Add table
Add a link
Reference in a new issue