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"
/>
-
-
-
-
-
- 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 && (
+
+
+
+
+ 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
+
+
+
+ )}
+
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.'
)}
downloadData(body, `${stackName}.yaml`)}
>
Get template file
-
+
- Already have your IAM roles setup?
+
+ {initialValues.integrationId
+ ? 'Already have your IAM roles setup?'
+ : 'After updating the stack, click below to proceed'}
+
Continue
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'));