diff --git a/web/src/components/buttons/LinkButton/LinkButton.tsx b/web/src/components/buttons/LinkButton/LinkButton.tsx index f57b77da32..c1573292e6 100644 --- a/web/src/components/buttons/LinkButton/LinkButton.tsx +++ b/web/src/components/buttons/LinkButton/LinkButton.tsx @@ -15,9 +15,8 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - import React from 'react'; -import { ButtonProps, Button, Box } from 'pouncejs'; +import { ButtonProps, Button, Box, LinkProps } from 'pouncejs'; import { Link as RRLink, LinkProps as RRLinkProps } from 'react-router-dom'; type ButtonWithoutAs = Omit; @@ -25,11 +24,20 @@ type ToProp = Pick; export type LinkButtonProps = ButtonWithoutAs & ToProp & { external?: boolean }; -const LinkButton: React.FC = ({ external, to, children, ...rest }) => { - const linkProps = external - ? { target: '_blank', rel: 'noopener noreferrer', href: to, as: 'a' as React.ElementType } - : { to, as: RRLink }; - +const LinkButton: React.FC = ({ disabled, external, to, children, ...rest }) => { + let linkProps: LinkProps; + if (disabled) { + linkProps = { as: 'span' as React.ElementType }; + } else if (!external) { + linkProps = { to, as: RRLink }; + } else { + linkProps = { + target: '_blank', + rel: 'noopener noreferrer', + href: to as string, + as: 'a' as React.ElementType, + }; + } return ( = ({ external, to, children, ...rest }, }} > - diff --git a/web/src/components/wizards/S3LogSourceWizard/S3SourceConfigurationPanel/S3SourceConfigurationPanel.tsx b/web/src/components/wizards/S3LogSourceWizard/S3SourceConfigurationPanel/S3SourceConfigurationPanel.tsx index 299fd98109..85dd9eecf7 100644 --- a/web/src/components/wizards/S3LogSourceWizard/S3SourceConfigurationPanel/S3SourceConfigurationPanel.tsx +++ b/web/src/components/wizards/S3LogSourceWizard/S3SourceConfigurationPanel/S3SourceConfigurationPanel.tsx @@ -71,8 +71,9 @@ const S3SourceConfigurationPanel: React.FC = () => { name="s3Bucket" as={FormikTextInput} label="Bucket Name" - required placeholder="The name of the S3 bucket that holds the logs" + disabled={!!initialValues.integrationId} + required /> . */ -import { Text, Box, Flex, SimpleGrid, Card, Img, Heading, Button, useSnackbar } from 'pouncejs'; +import { Text, Box, Flex, Card, Img, Heading, Button, useSnackbar } from 'pouncejs'; import React from 'react'; import { downloadData, toStackNameFormat } from 'Helpers/utils'; import { useFormikContext } from 'formik'; @@ -32,7 +32,7 @@ const StackDeployment: React.FC = () => { const { pushSnackbar } = useSnackbar(); const { goToNextStep } = useWizardContext(); const { initialValues, values } = useFormikContext(); - const { data, loading } = useGetLogCfnTemplate({ + const { data, loading, error } = useGetLogCfnTemplate({ variables: { input: { awsAccountId: pantherConfig.AWS_ACCOUNT_ID, @@ -63,60 +63,70 @@ const StackDeployment: React.FC = () => { title="Deploy Panther's IAM roles" subtitle="These roles will allow Panther to read your logs from the S3 Bucket" /> - - - - Lightning - - Using Cloudformation Console - - - Deploy our autogenerated Cloudformation template to the AWS account that you are - onboarding, to generate the necessary ReadOnly IAM Roles. After deployment please - continue with setup completion. - {initialValues.integrationId && ( - - Make sure you select Update and then Replace current template - - )} - - - Launch Console - - - - + + {!initialValues.integrationId && ( + + + Lightning + + Using Cloudformation Console + + + Deploy our autogenerated Cloudformation template to the AWS account that you are + onboarding, to generate the necessary ReadOnly IAM Roles. After deployment please + continue with setup completion. + + + Launch Console + + + + )} + Cogssn Using the AWS CLI - Download the autogenerated Cloudformation template and deploy it to the AWS account - that you are onboarding via the given CLI/SDK. After deployment please continue with - setup completion. - {initialValues.integrationId && ( - - Make sure you update the template of the existing stack - + {initialValues.integrationId ? ( + + Download the updated CloudFormation template and update the CloudFormation stack + created in the bucket's region during the onboarding process for this source. + + The default stack name would be: {initialValues.initialStackName} + + + ) : ( + 'Download the autogenerated Cloudformation template and deploy it to the AWS account that you are onboarding via the given CLI/SDK. After deployment please continue with setup completion.' )} - + - Already have your IAM roles setup? + + {initialValues.integrationId + ? 'Already have your IAM roles setup?' + : 'After updating the stack, click below to proceed'} + diff --git a/web/src/pages/CreateLogSource/CreateS3LogSource/CreateS3LogSource.test.tsx b/web/src/pages/CreateLogSource/CreateS3LogSource/CreateS3LogSource.test.tsx index 589babdaf4..d5585e0739 100644 --- a/web/src/pages/CreateLogSource/CreateS3LogSource/CreateS3LogSource.test.tsx +++ b/web/src/pages/CreateLogSource/CreateS3LogSource/CreateS3LogSource.test.tsx @@ -26,10 +26,13 @@ import { waitMs, buildListAvailableLogTypesResponse, buildAddS3LogIntegrationInput, + buildIntegrationTemplate, } from 'test-utils'; import { EventEnum, SrcEnum, trackError, TrackErrorEnum, trackEvent } from 'Helpers/analytics'; import { LOG_ONBOARDING_SNS_DOC_URL } from 'Source/constants'; import { mockListAvailableLogTypes } from 'Source/graphql/queries'; +import { mockGetLogCfnTemplate } from 'Components/wizards/S3LogSourceWizard'; +import { pantherConfig } from 'Source/config'; import { mockAddS3LogSource } from './graphql/addS3LogSource.generated'; import CreateS3LogSource from './CreateS3LogSource'; @@ -50,6 +53,21 @@ describe('CreateS3LogSource', () => { listAvailableLogTypes: logTypesResponse, }, }), + mockGetLogCfnTemplate({ + variables: { + input: { + awsAccountId: pantherConfig.AWS_ACCOUNT_ID, + integrationLabel: logSource.integrationLabel, + s3Bucket: logSource.s3Bucket, + logTypes: logSource.logTypes, + kmsKey: null, + s3Prefix: null, + }, + }, + data: { + getS3LogIntegrationTemplate: buildIntegrationTemplate(), + }, + }), mockAddS3LogSource({ variables: { input: buildAddS3LogIntegrationInput({ @@ -84,11 +102,13 @@ describe('CreateS3LogSource', () => { await waitMs(50); fireEvent.click(getByText('Continue Setup')); - // Initially we expect a disabled button while the template is being fetched ... + // Initially we expect 2 disabled buttons while the template is being fetched ... expect(getByText('Get template file')).toHaveAttribute('disabled'); + expect(getByText('Launch Console')).toHaveAttribute('aria-disabled', 'true'); // ... replaced by an active button as soon as it's fetched await waitFor(() => expect(getByText('Get template file')).not.toHaveAttribute('disabled')); + expect(getByText('Launch Console')).toHaveAttribute('aria-disabled', 'false'); // We move on to the final screen fireEvent.click(getByText('Continue')); diff --git "a/web/src/pages/EditS3LogSource/\316\225ditS3LogSource.test.tsx" "b/web/src/pages/EditS3LogSource/\316\225ditS3LogSource.test.tsx" index c303b4717f..6499a86d9e 100644 --- "a/web/src/pages/EditS3LogSource/\316\225ditS3LogSource.test.tsx" +++ "b/web/src/pages/EditS3LogSource/\316\225ditS3LogSource.test.tsx" @@ -25,11 +25,14 @@ import { waitMs, buildListAvailableLogTypesResponse, buildUpdateS3LogIntegrationInput, + buildIntegrationTemplate, } from 'test-utils'; import { Route } from 'react-router'; import urls from 'Source/urls'; import { mockListAvailableLogTypes } from 'Source/graphql/queries'; import { EventEnum, SrcEnum, trackEvent } from 'Helpers/analytics'; +import { mockGetLogCfnTemplate } from 'Components/wizards/S3LogSourceWizard'; +import { pantherConfig } from 'Source/config'; import EditS3LogSource from './EditS3LogSource'; import { mockGetS3LogSource } from './graphql/getS3LogSource.generated'; import { mockUpdateS3LogSource } from './graphql/updateS3LogSource.generated'; @@ -42,7 +45,7 @@ describe('EditS3LogSource', () => { const logSource = buildS3LogIntegration({ awsAccountId: '123123123123', logTypes: logTypesResponse.logTypes, - kmsKey: '', + kmsKey: null, }); const updatedLogSource = buildS3LogIntegration({ ...logSource, integrationLabel: 'new-value' }); @@ -61,14 +64,29 @@ describe('EditS3LogSource', () => { listAvailableLogTypes: logTypesResponse, }, }), + mockGetLogCfnTemplate({ + variables: { + input: { + awsAccountId: pantherConfig.AWS_ACCOUNT_ID, + integrationLabel: updatedLogSource.integrationLabel, + s3Bucket: updatedLogSource.s3Bucket, + logTypes: updatedLogSource.logTypes, + kmsKey: updatedLogSource.kmsKey, + s3Prefix: updatedLogSource.s3Prefix, + }, + }, + data: { + getS3LogIntegrationTemplate: buildIntegrationTemplate(), + }, + }), mockUpdateS3LogSource({ variables: { input: buildUpdateS3LogIntegrationInput({ - integrationId: logSource.integrationId, + integrationId: updatedLogSource.integrationId, integrationLabel: updatedLogSource.integrationLabel, - s3Bucket: logSource.s3Bucket, - logTypes: logSource.logTypes, - s3Prefix: logSource.s3Prefix, + s3Bucket: updatedLogSource.s3Bucket, + logTypes: updatedLogSource.logTypes, + s3Prefix: updatedLogSource.s3Prefix, kmsKey: null, }), }, @@ -77,7 +95,7 @@ describe('EditS3LogSource', () => { }, }), ]; - const { getByText, getByLabelText, getByAltText, findByText } = render( + const { getByText, getByLabelText, getByAltText, findByText, queryByText } = render( , @@ -106,6 +124,9 @@ describe('EditS3LogSource', () => { // ... replaced by an active button as soon as it's fetched await waitFor(() => expect(getByText('Get template file')).not.toHaveAttribute('disabled')); + // We expect not to display a link button AWS Console for editing + expect(queryByText('Launch Console')).not.toBeInTheDocument(); + // We move on to the final screen fireEvent.click(getByText('Continue'));