From f6b2f307586f079dfd16b1e72a1aa91d23256be3 Mon Sep 17 00:00:00 2001 From: Anemy Date: Thu, 6 Jan 2022 11:57:19 -0500 Subject: [PATCH 1/9] Add tls ssl form fields --- package-lock.json | 90 +++++++++++++ packages/compass-components/package.json | 1 + .../src/components/file-input.tsx | 127 ++++++++++++------ packages/compass-components/src/index.ts | 1 + .../ssh-tunnel-tab/ssh-tunnel-tab.tsx | 1 + .../tls-ssl-tab/tls-certificate-authority.tsx | 109 +++++++++++++++ .../tls-ssl-tab/tls-client-certificate.tsx | 113 ++++++++++++++++ .../tls-ssl-tab/tls-ssl-tab.tsx | 65 ++++++++- 8 files changed, 461 insertions(+), 46 deletions(-) create mode 100644 packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx create mode 100644 packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-client-certificate.tsx diff --git a/package-lock.json b/package-lock.json index 0df673a897e..aa826f962f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5447,6 +5447,52 @@ "node": ">=10" } }, + "node_modules/@leafygreen-ui/radio-group": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/radio-group/-/radio-group-7.0.6.tgz", + "integrity": "sha512-LsokgU+fX2b9teMeiE0VPNH6K9RsGOwesmiZr40q3Df+LWVTMqsjWqIMhXWe1gAwgqqHewdkaXb4eLCLG3rs2w==", + "dependencies": { + "@leafygreen-ui/hooks": "^7.0.0", + "@leafygreen-ui/interaction-ring": "^1.1.0", + "@leafygreen-ui/lib": "^9.0.0", + "@leafygreen-ui/palette": "^3.2.2" + } + }, + "node_modules/@leafygreen-ui/radio-group/node_modules/@leafygreen-ui/emotion": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-4.0.0.tgz", + "integrity": "sha512-nr2g6OFsy+psaMto3H4HQ1ivM1tCwd9k1bbR5WH4U7YibDagfBekFTwlhohmC/K7hUM/eDVPGw0w4zQyC+BwZg==", + "dependencies": { + "@emotion/css": "^11.1.3", + "@emotion/react": "^11.4.0", + "@emotion/server": "^11.4.0" + } + }, + "node_modules/@leafygreen-ui/radio-group/node_modules/@leafygreen-ui/lib": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-9.1.0.tgz", + "integrity": "sha512-JH3mnoCZNtUcJfXrvVG4I3PAIm1ehlR1H5WkDqkjengcf/iVMo7+AluzwfTCQb2fqQgohURs+qY4vEhJe+9+2g==", + "dependencies": { + "@leafygreen-ui/emotion": "^4.0.0", + "facepaint": "^1.2.1", + "polished": "^4.1.3", + "prop-types": "^15.0.0" + }, + "peerDependencies": { + "react": "^17.0.0" + } + }, + "node_modules/@leafygreen-ui/radio-group/node_modules/polished": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.1.3.tgz", + "integrity": "sha512-ocPAcVBUOryJEKe0z2KLd1l9EBa1r5mSwlKpExmrLzsnIzJo4axsoU9O2BjOTkDGDT4mZ0WFE5XKTlR3nLnZOA==", + "dependencies": { + "@babel/runtime": "^7.14.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@leafygreen-ui/ripple": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@leafygreen-ui/ripple/-/ripple-1.1.1.tgz", @@ -63291,6 +63337,7 @@ "@leafygreen-ui/palette": "^3.2.2", "@leafygreen-ui/portal": "^3.1.3", "@leafygreen-ui/radio-box-group": "^6.1.4", + "@leafygreen-ui/radio-group": "^7.0.6", "@leafygreen-ui/segmented-control": "^0.9.1", "@leafygreen-ui/select": "^3.0.8", "@leafygreen-ui/tabs": "^5.1.3", @@ -107664,6 +107711,48 @@ } } }, + "@leafygreen-ui/radio-group": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/radio-group/-/radio-group-7.0.6.tgz", + "integrity": "sha512-LsokgU+fX2b9teMeiE0VPNH6K9RsGOwesmiZr40q3Df+LWVTMqsjWqIMhXWe1gAwgqqHewdkaXb4eLCLG3rs2w==", + "requires": { + "@leafygreen-ui/hooks": "^7.0.0", + "@leafygreen-ui/interaction-ring": "^1.1.0", + "@leafygreen-ui/lib": "^9.0.0", + "@leafygreen-ui/palette": "^3.2.2" + }, + "dependencies": { + "@leafygreen-ui/emotion": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/emotion/-/emotion-4.0.0.tgz", + "integrity": "sha512-nr2g6OFsy+psaMto3H4HQ1ivM1tCwd9k1bbR5WH4U7YibDagfBekFTwlhohmC/K7hUM/eDVPGw0w4zQyC+BwZg==", + "requires": { + "@emotion/css": "^11.1.3", + "@emotion/react": "^11.4.0", + "@emotion/server": "^11.4.0" + } + }, + "@leafygreen-ui/lib": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-9.1.0.tgz", + "integrity": "sha512-JH3mnoCZNtUcJfXrvVG4I3PAIm1ehlR1H5WkDqkjengcf/iVMo7+AluzwfTCQb2fqQgohURs+qY4vEhJe+9+2g==", + "requires": { + "@leafygreen-ui/emotion": "^4.0.0", + "facepaint": "^1.2.1", + "polished": "^4.1.3", + "prop-types": "^15.0.0" + } + }, + "polished": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.1.3.tgz", + "integrity": "sha512-ocPAcVBUOryJEKe0z2KLd1l9EBa1r5mSwlKpExmrLzsnIzJo4axsoU9O2BjOTkDGDT4mZ0WFE5XKTlR3nLnZOA==", + "requires": { + "@babel/runtime": "^7.14.0" + } + } + } + }, "@leafygreen-ui/ripple": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@leafygreen-ui/ripple/-/ripple-1.1.1.tgz", @@ -113572,6 +113661,7 @@ "@leafygreen-ui/palette": "^3.2.2", "@leafygreen-ui/portal": "^3.1.3", "@leafygreen-ui/radio-box-group": "^6.1.4", + "@leafygreen-ui/radio-group": "^7.0.6", "@leafygreen-ui/segmented-control": "^0.9.1", "@leafygreen-ui/select": "^3.0.8", "@leafygreen-ui/tabs": "^5.1.3", diff --git a/packages/compass-components/package.json b/packages/compass-components/package.json index f4f1306e8e0..cccc313da43 100644 --- a/packages/compass-components/package.json +++ b/packages/compass-components/package.json @@ -51,6 +51,7 @@ "@leafygreen-ui/palette": "^3.2.2", "@leafygreen-ui/portal": "^3.1.3", "@leafygreen-ui/radio-box-group": "^6.1.4", + "@leafygreen-ui/radio-group": "^7.0.6", "@leafygreen-ui/segmented-control": "^0.9.1", "@leafygreen-ui/select": "^3.0.8", "@leafygreen-ui/tabs": "^5.1.3", diff --git a/packages/compass-components/src/components/file-input.tsx b/packages/compass-components/src/components/file-input.tsx index 0c94a5d27df..d985ff80e30 100644 --- a/packages/compass-components/src/components/file-input.tsx +++ b/packages/compass-components/src/components/file-input.tsx @@ -17,21 +17,26 @@ const formItemHorizontalStyles = css({ alignItems: 'center', }); -const formItemVerticalStyles = css` - margin: 5px auto 20px; -`; - -const formItemErrorStyles = css` - border: 1px solid ${redBaseColor}; - border-radius: 5px; - &:focus { - border: 1px solid ${redBaseColor}; +const formItemVerticalStyles = css({ + margin: '5px auto 20px' +}); + +const formItemErrorStyles = css({ + border: `1px solid ${redBaseColor}`, + borderRadius: '5px', + ['&:focus']: { + border: `1px solid ${redBaseColor}` } -`; +}); -const buttonStyles = css` - width: 100%; -`; +const buttonStyles = css({ + width: '100%' +}); + +const disabledButtonStyles = css({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + pointerEvents: 'auto !important' as any // !important to override leafygreen styles misordering. +}); const errorMessageStyles = css({ color: `${redBaseColor} !important`, @@ -47,31 +52,38 @@ const labelHorizontalStyles = css({ paddingRight: spacing[3], }); -const labelIconStyles = css` - display: inline-block; - vertical-align: middle; - font: normal normal normal 14px/1 FontAwesome; - font-size: inherit; - text-rendering: auto; - margin: 0 0 0 5px; - cursor: pointer; - color: #bfbfbe; - - &:link, - &:active { - color: #bfbfbe; - } +const labelIconStyles = css({ + display: 'inline-block', + verticalAlign: 'middle', + font: 'normal normal normal 14px/1 FontAwesome', + fontSize: 'inherit', + textRendering: 'auto', + margin: '0 0 0 5px', + cursor: 'pointer', + color: '#bfbfbe', + + '&:link, &:active': { + color: '#bfbfbe', + }, + + '&:link, &:active, &:hover': { + textDecoration: 'none' + }, - &:link, - &:active, - &:hover { - text-decoration: none; + '&:hover': { + color: '#fbb129', } +}); - &:hover { - color: #fbb129; +const disabledLabelStyles = css({ + '&:first-child': { + pointerEvents: 'none' } -`; +}); + +const disabledDescriptionStyles = css({ + color: uiColors.gray.dark1 +}) export enum Variant { Horizontal = 'HORIZONTAL', @@ -89,6 +101,7 @@ function FileInput({ label, dataTestId, onChange, + disabled, multi = false, error = false, errorMessage, @@ -100,8 +113,9 @@ function FileInput({ }: { id: string; label: string; - dataTestId: string; + dataTestId?: string; onChange: (files: string[]) => void; + disabled?: boolean; multi?: boolean; error?: boolean; errorMessage?: string; @@ -132,9 +146,9 @@ function FileInput({ [onChange] ); - const renderDescription = () => { + const renderDescription = (): React.ReactElement | null => { if (!link && !description) { - return <>; + return null; } if (!link) { return ( @@ -174,18 +188,39 @@ function FileInput({ { [formItemErrorStyles]: error } )} > - + { - if (inputRef.current) { + if (!disabled && inputRef.current) { inputRef.current.click(); } }} diff --git a/packages/compass-components/src/index.ts b/packages/compass-components/src/index.ts index 5d319a12751..5a1a15602d7 100644 --- a/packages/compass-components/src/index.ts +++ b/packages/compass-components/src/index.ts @@ -33,6 +33,7 @@ export { uiColors } from '@leafygreen-ui/palette'; export * as compassUIColors from './compass-ui-colors'; export { default as Portal } from '@leafygreen-ui/portal'; export { RadioBox, RadioBoxGroup } from '@leafygreen-ui/radio-box-group'; +export { Radio, RadioGroup } from '@leafygreen-ui/radio-group'; export { Select, Option, Size as SelectSize } from '@leafygreen-ui/select'; export { Tabs, Tab } from '@leafygreen-ui/tabs'; export { default as TextArea } from '@leafygreen-ui/text-area'; diff --git a/packages/connect-form/src/components/advanced-options-tabs/ssh-tunnel-tab/ssh-tunnel-tab.tsx b/packages/connect-form/src/components/advanced-options-tabs/ssh-tunnel-tab/ssh-tunnel-tab.tsx index efeb2abd49e..652bf6d7799 100644 --- a/packages/connect-form/src/components/advanced-options-tabs/ssh-tunnel-tab/ssh-tunnel-tab.tsx +++ b/packages/connect-form/src/components/advanced-options-tabs/ssh-tunnel-tab/ssh-tunnel-tab.tsx @@ -74,6 +74,7 @@ const containerStyles = css({ const contentStyles = css({ marginTop: spacing[3], width: '50%', + minWidth: 400 }); function SSHTunnel({ diff --git a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx new file mode 100644 index 00000000000..cdec996e046 --- /dev/null +++ b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx @@ -0,0 +1,109 @@ +import { css } from '@emotion/css'; +import React, { useCallback, useMemo, useState } from 'react'; +import { + Checkbox, + Description, + FileInput, + Icon, + IconButton, + Label, + RadioBox, + RadioBoxGroup, + Radio, + RadioGroup, + TextInput, + spacing, +} from '@mongodb-js/compass-components'; +import ConnectionStringUrl from 'mongodb-connection-string-url'; +import type { MongoClientOptions } from 'mongodb'; + +import { UpdateConnectionFormField } from '../../../hooks/use-connect-form'; +import FormFieldContainer from '../../form-field-container'; +import { TLS_OPTIONS } from '../../../constants/ssl-tls-options'; + +const caFieldsContainer = css({ + marginLeft: spacing[3], +}); + +enum TLS_CA_OPTIONS { + DEFAULT = 'DEFAULT', + CUSTOM = 'CUSTOM' +} + +function TLSCertificateAuthority({ + connectionStringUrl, + disabled, + updateConnectionFormField, +}: { + connectionStringUrl: ConnectionStringUrl; + disabled: boolean; + updateConnectionFormField: UpdateConnectionFormField; +}): React.ReactElement { + const [ useCustomCA, setUseCustomCA ] = useState( + connectionStringUrl.searchParams.get('tlsCAFile') !== null + ); + + const onChangeTLSOption = useCallback( + (event: React.ChangeEvent) => { + updateConnectionFormField({ + type: 'update-tls-option', + tlsOption: event.target.value as TLS_OPTIONS, + }); + }, + [updateConnectionFormField] + ); + + const caFileOptionsDisabled = useMemo( + () => disabled || !useCustomCA, [disabled, useCustomCA] + ); + + return ( + + ) => { + setUseCustomCA(event.target.value === TLS_CA_OPTIONS.CUSTOM) + }} + value={useCustomCA ? TLS_CA_OPTIONS.CUSTOM : TLS_CA_OPTIONS.DEFAULT} + > + + Accept any server TLS/SSL certificates + + + Use own root certificate from the Certificate Authority + + +
+ { + alert(`selected ${files.join(',')}`); + // formFieldChanged(name as IdentityFormKeys, files[0]); + }} + // label={label} + // error={Boolean(errorMessage)} + // errorMessage={errorMessage} + // values={value as string[] | undefined} + // description={'Learn More'} + // link={'https://mongodb.com'} + /> +
+
+ ); +} + +export default TLSCertificateAuthority; diff --git a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-client-certificate.tsx b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-client-certificate.tsx new file mode 100644 index 00000000000..183f22541e7 --- /dev/null +++ b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-client-certificate.tsx @@ -0,0 +1,113 @@ +import { css } from '@emotion/css'; +import React, { useCallback, useMemo, useState } from 'react'; +import { + Checkbox, + Description, + FileInput, + Icon, + IconButton, + Label, + RadioBox, + RadioBoxGroup, + TextInput, + spacing, +} from '@mongodb-js/compass-components'; +import ConnectionStringUrl from 'mongodb-connection-string-url'; +import type { MongoClientOptions } from 'mongodb'; + +import { UpdateConnectionFormField } from '../../../hooks/use-connect-form'; +import FormFieldContainer from '../../form-field-container'; +import { TLS_OPTIONS } from '../../../constants/ssl-tls-options'; + +const clientCertificateFieldsContainer = css({ + marginLeft: spacing[3], +}); + +function TLSClientCertificate({ + connectionStringUrl, + disabled, + updateConnectionFormField, +}: { + connectionStringUrl: ConnectionStringUrl; + disabled: boolean; + updateConnectionFormField: UpdateConnectionFormField; +}): React.ReactElement { + const [ useClientCert, setUseClientCert ] = useState( + connectionStringUrl.searchParams.get('tlsCertificateKeyFile') !== null + ); + // TODO: Override when underlying connection changes? + + const clientCertificateOptionsDisabled = useMemo( + () => disabled || !useClientCert, [disabled, useClientCert] + ); + + return ( + + ) => setUseClientCert(event.target.checked)} + // id={name} + // dataTestId={name} + // onChange={(files: string[]) => { + // formFieldChanged(name as IdentityFormKeys, files[0]); + // }} + // label={label} + // error={Boolean(errorMessage)} + // errorMessage={errorMessage} + // values={value as string[] | undefined} + // description={'Learn More'} + // link={'https://mongodb.com'} + /> +
+ + { + alert(`client cert change ${files.join(',')}`) + // formFieldChanged(name as IdentityFormKeys, files[0]); + }} + // label={label} + // error={Boolean(errorMessage)} + // errorMessage={errorMessage} + // values={value as string[] | undefined} + // description={'Learn More'} + // link={'https://mongodb.com'} + /> + + + ) => { + // formFieldChanged( + // name as IdentityFormKeys, + // name === 'port' ? Number(value) : value + // ); + // }} + disabled={clientCertificateOptionsDisabled} + label="Client Key Password" + // https://docs.mongodb.com/manual/reference/connection-string/#mongodb-urioption-urioption.tlsCertificateKeyFilePassword + type="password" + value={connectionStringUrl.searchParams.get('tlsCertificateKeyFilePassword') || ''} + // value={} + // errorMessage={errorMessage} + // state={state as 'error' | 'none'} + /> + +
+
+ ); +} + +export default TLSClientCertificate; diff --git a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx index 444b6993fcb..5f1439c1b35 100644 --- a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx +++ b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx @@ -1,7 +1,10 @@ import { css } from '@emotion/css'; import React, { useCallback } from 'react'; import { + Checkbox, Description, + Icon, + IconButton, Label, RadioBox, RadioBoxGroup, @@ -13,11 +16,19 @@ import type { MongoClientOptions } from 'mongodb'; import { UpdateConnectionFormField } from '../../../hooks/use-connect-form'; import FormFieldContainer from '../../form-field-container'; import { TLS_OPTIONS } from '../../../constants/ssl-tls-options'; +import TLSClientCertificate from './tls-client-certificate'; +import TLSCertificateAuthority from './tls-certificate-authority'; -const descriptionStyles = css({ +const tlsDescriptionStyles = css({ marginTop: spacing[1], }); +const infoButtonStyles = css({ + verticalAlign: 'middle', + marginTop: -spacing[1], + marginBottom: -spacing[1], +}); + const TLS_TYPES: { value: TLS_OPTIONS; label: string; @@ -96,11 +107,37 @@ function TLSTab({ [updateConnectionFormField] ); + const tlsOptionFields = [ + { + name: 'tlsInsecure', + description: 'This includes tlsAllowInvalidHostnames and tlsAllowInvalidCertificates. This is not recommended as disabling certificate validation creates a vulnerability.', + checked: connectionStringUrl.searchParams.get('tlsInsecure') === 'true' + }, { + name: 'tlsAllowInvalidHostnames', + description: 'Disables the validation of the hostnames in the certificate presented by the mongod/mongos instance', + checked: connectionStringUrl.searchParams.get('tlsAllowInvalidHostnames') === 'true' + }, { + name: 'tlsAllowInvalidCertificates', + description: 'This disables validating the server certificates. This is not recommended as it creates a vulnerability to expired mongod and mongos certificates as well as to foreign processes posing as valid mongod or mongos instances.', + checked: connectionStringUrl.searchParams.get('tlsAllowInvalidCertificates') === 'true' + } + ]; + + const tlsOptionsDisabled = tlsOption !== 'ON'; + return (
{TLS_TYPES.map((tlsType) => ( @@ -109,11 +146,35 @@ function TLSTab({ ))} - + {TLS_TYPES.find((tlsType) => tlsType.value === tlsOption) ?.description || ''} + + + {tlsOptionFields.map(tlsOptionField => ( + + alert(`update ${tlsOptionField.name}`)} + label={tlsOptionField.name} + disabled={tlsOptionsDisabled} + checked={tlsOptionField.checked} + bold={false} + /> + + {tlsOptionField.description} + + + ))}
); } From 46e7a329e86e095adbba09385969da099887f2e1 Mon Sep 17 00:00:00 2001 From: Anemy Date: Fri, 7 Jan 2022 13:07:25 -0500 Subject: [PATCH 2/9] styling updates --- .../general/host-input.tsx | 6 +- .../ssh-tunnel-tab/ssh-tunnel-tab.tsx | 2 +- .../tls-ssl-tab/tls-certificate-authority.tsx | 97 +++++++-------- .../tls-ssl-tab/tls-client-certificate.tsx | 117 +++++++----------- .../tls-ssl-tab/tls-ssl-tab.tsx | 43 +++++-- .../src/components/form-field-container.tsx | 8 +- 6 files changed, 131 insertions(+), 142 deletions(-) diff --git a/packages/connect-form/src/components/advanced-options-tabs/general/host-input.tsx b/packages/connect-form/src/components/advanced-options-tabs/general/host-input.tsx index 79f509e9207..f0bc94d9018 100644 --- a/packages/connect-form/src/components/advanced-options-tabs/general/host-input.tsx +++ b/packages/connect-form/src/components/advanced-options-tabs/general/host-input.tsx @@ -32,6 +32,10 @@ const hostActionButtonStyles = css({ marginTop: spacing[1], }); +const inputFieldStyles = css({ + width: '50%', +}); + function HostInput({ errors, connectionStringUrl, @@ -79,7 +83,7 @@ function HostInput({ return ( <> - + diff --git a/packages/connect-form/src/components/advanced-options-tabs/ssh-tunnel-tab/ssh-tunnel-tab.tsx b/packages/connect-form/src/components/advanced-options-tabs/ssh-tunnel-tab/ssh-tunnel-tab.tsx index 652bf6d7799..c233cebf5a0 100644 --- a/packages/connect-form/src/components/advanced-options-tabs/ssh-tunnel-tab/ssh-tunnel-tab.tsx +++ b/packages/connect-form/src/components/advanced-options-tabs/ssh-tunnel-tab/ssh-tunnel-tab.tsx @@ -74,7 +74,7 @@ const containerStyles = css({ const contentStyles = css({ marginTop: spacing[3], width: '50%', - minWidth: 400 + minWidth: 400, }); function SSHTunnel({ diff --git a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx index cdec996e046..73d3cfe3cf9 100644 --- a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx +++ b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx @@ -22,13 +22,20 @@ import FormFieldContainer from '../../form-field-container'; import { TLS_OPTIONS } from '../../../constants/ssl-tls-options'; const caFieldsContainer = css({ - marginLeft: spacing[3], + // display: 'flex', + // flexDirection: 'row', + // alignItems: 'center', + width: '50%', }); -enum TLS_CA_OPTIONS { - DEFAULT = 'DEFAULT', - CUSTOM = 'CUSTOM' -} +// const caFileInputContainer = css({ +// flexGrow: 1 +// }); + +// const removeFileButtonStyles = css({ +// marginLeft: spacing[1], +// marginTop: spacing[1], +// }); function TLSCertificateAuthority({ connectionStringUrl, @@ -39,7 +46,8 @@ function TLSCertificateAuthority({ disabled: boolean; updateConnectionFormField: UpdateConnectionFormField; }): React.ReactElement { - const [ useCustomCA, setUseCustomCA ] = useState( + const [caFile, setCAFile] = useState(undefined); + const [useCustomCA, setUseCustomCA] = useState( connectionStringUrl.searchParams.get('tlsCAFile') !== null ); @@ -53,55 +61,38 @@ function TLSCertificateAuthority({ [updateConnectionFormField] ); - const caFileOptionsDisabled = useMemo( - () => disabled || !useCustomCA, [disabled, useCustomCA] - ); - return ( - - ) => { - setUseCustomCA(event.target.value === TLS_CA_OPTIONS.CUSTOM) - }} - value={useCustomCA ? TLS_CA_OPTIONS.CUSTOM : TLS_CA_OPTIONS.DEFAULT} - > - - Accept any server TLS/SSL certificates - - - Use own root certificate from the Certificate Authority - - -
+ {/*
- { - alert(`selected ${files.join(',')}`); - // formFieldChanged(name as IdentityFormKeys, files[0]); - }} - // label={label} - // error={Boolean(errorMessage)} - // errorMessage={errorMessage} - // values={value as string[] | undefined} - // description={'Learn More'} - // link={'https://mongodb.com'} - /> -
+ > */} + { + setCAFile(files); + // formFieldChanged(name as IdentityFormKeys, files[0]); + }} + values={caFile} + /> + {/*
*/} + {/* {caFile && ( + setCAFile(undefined) } + > + + + )} */} + {/* */}
); } diff --git a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-client-certificate.tsx b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-client-certificate.tsx index 183f22541e7..5b7c61f9037 100644 --- a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-client-certificate.tsx +++ b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-client-certificate.tsx @@ -17,10 +17,9 @@ import type { MongoClientOptions } from 'mongodb'; import { UpdateConnectionFormField } from '../../../hooks/use-connect-form'; import FormFieldContainer from '../../form-field-container'; -import { TLS_OPTIONS } from '../../../constants/ssl-tls-options'; -const clientCertificateFieldsContainer = css({ - marginLeft: spacing[3], +const inputFieldStyles = css({ + width: '50%', }); function TLSClientCertificate({ @@ -32,81 +31,53 @@ function TLSClientCertificate({ disabled: boolean; updateConnectionFormField: UpdateConnectionFormField; }): React.ReactElement { - const [ useClientCert, setUseClientCert ] = useState( + const [useClientCert, setUseClientCert] = useState( connectionStringUrl.searchParams.get('tlsCertificateKeyFile') !== null ); // TODO: Override when underlying connection changes? - const clientCertificateOptionsDisabled = useMemo( - () => disabled || !useClientCert, [disabled, useClientCert] - ); - return ( - - ) => setUseClientCert(event.target.checked)} - // id={name} - // dataTestId={name} - // onChange={(files: string[]) => { - // formFieldChanged(name as IdentityFormKeys, files[0]); - // }} - // label={label} - // error={Boolean(errorMessage)} - // errorMessage={errorMessage} - // values={value as string[] | undefined} - // description={'Learn More'} - // link={'https://mongodb.com'} - /> -
- - { - alert(`client cert change ${files.join(',')}`) - // formFieldChanged(name as IdentityFormKeys, files[0]); - }} - // label={label} - // error={Boolean(errorMessage)} - // errorMessage={errorMessage} - // values={value as string[] | undefined} - // description={'Learn More'} - // link={'https://mongodb.com'} - /> - - - ) => { - // formFieldChanged( - // name as IdentityFormKeys, - // name === 'port' ? Number(value) : value - // ); - // }} - disabled={clientCertificateOptionsDisabled} - label="Client Key Password" - // https://docs.mongodb.com/manual/reference/connection-string/#mongodb-urioption-urioption.tlsCertificateKeyFilePassword - type="password" - value={connectionStringUrl.searchParams.get('tlsCertificateKeyFilePassword') || ''} - // value={} - // errorMessage={errorMessage} - // state={state as 'error' | 'none'} - /> - -
-
+ <> + + { + alert(`client cert change ${files.join(',')}`); + // formFieldChanged(name as IdentityFormKeys, files[0]); + }} + /> + + + ) => { + // formFieldChanged( + // name as IdentityFormKeys, + // name === 'port' ? Number(value) : value + // ); + // }} + disabled={disabled} + label="Client Key Password" + // https://docs.mongodb.com/manual/reference/connection-string/#mongodb-urioption-urioption.tlsCertificateKeyFilePassword + type="password" + value={ + connectionStringUrl.searchParams.get( + 'tlsCertificateKeyFilePassword' + ) || '' + } + /> + + ); } diff --git a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx index 5f1439c1b35..b45ae9f4ea8 100644 --- a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx +++ b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx @@ -1,4 +1,4 @@ -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import React, { useCallback } from 'react'; import { Checkbox, @@ -9,6 +9,7 @@ import { RadioBox, RadioBoxGroup, spacing, + uiColors, } from '@mongodb-js/compass-components'; import ConnectionStringUrl from 'mongodb-connection-string-url'; import type { MongoClientOptions } from 'mongodb'; @@ -29,6 +30,10 @@ const infoButtonStyles = css({ marginBottom: -spacing[1], }); +const disabledCheckboxDescriptionStyles = css({ + color: uiColors.gray.light1, +}); + const TLS_TYPES: { value: TLS_OPTIONS; label: string; @@ -110,17 +115,26 @@ function TLSTab({ const tlsOptionFields = [ { name: 'tlsInsecure', - description: 'This includes tlsAllowInvalidHostnames and tlsAllowInvalidCertificates. This is not recommended as disabling certificate validation creates a vulnerability.', - checked: connectionStringUrl.searchParams.get('tlsInsecure') === 'true' - }, { + description: + 'This includes tlsAllowInvalidHostnames and tlsAllowInvalidCertificates. This is not recommended as disabling certificate validation creates a vulnerability.', + checked: connectionStringUrl.searchParams.get('tlsInsecure') === 'true', + }, + { name: 'tlsAllowInvalidHostnames', - description: 'Disables the validation of the hostnames in the certificate presented by the mongod/mongos instance', - checked: connectionStringUrl.searchParams.get('tlsAllowInvalidHostnames') === 'true' - }, { + description: + 'Disables the validation of the hostnames in the certificate presented by the mongod/mongos instance', + checked: + connectionStringUrl.searchParams.get('tlsAllowInvalidHostnames') === + 'true', + }, + { name: 'tlsAllowInvalidCertificates', - description: 'This disables validating the server certificates. This is not recommended as it creates a vulnerability to expired mongod and mongos certificates as well as to foreign processes posing as valid mongod or mongos instances.', - checked: connectionStringUrl.searchParams.get('tlsAllowInvalidCertificates') === 'true' - } + description: + 'This disables validating the server certificates. This is not recommended as it creates a vulnerability to expired mongod and mongos certificates as well as to foreign processes posing as valid mongod or mongos instances.', + checked: + connectionStringUrl.searchParams.get('tlsAllowInvalidCertificates') === + 'true', + }, ]; const tlsOptionsDisabled = tlsOption !== 'ON'; @@ -161,16 +175,21 @@ function TLSTab({ disabled={tlsOptionsDisabled} updateConnectionFormField={updateConnectionFormField} /> - {tlsOptionFields.map(tlsOptionField => ( + {tlsOptionFields.map((tlsOptionField) => ( alert(`update ${tlsOptionField.name}`)} label={tlsOptionField.name} disabled={tlsOptionsDisabled} checked={tlsOptionField.checked} bold={false} /> - + {tlsOptionField.description} diff --git a/packages/connect-form/src/components/form-field-container.tsx b/packages/connect-form/src/components/form-field-container.tsx index 939e1fef124..e0452912b30 100644 --- a/packages/connect-form/src/components/form-field-container.tsx +++ b/packages/connect-form/src/components/form-field-container.tsx @@ -1,4 +1,4 @@ -import { css } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import { spacing } from '@mongodb-js/compass-components'; import React from 'react'; @@ -7,11 +7,15 @@ const formFieldContainerStyles = css({ }); function FormFieldContainer({ + className = '', children, }: { + className?: string; children: React.ReactNode; }): React.ReactElement { - return
{children}
; + return ( +
{children}
+ ); } export default FormFieldContainer; From 9e7d8dde2dabf6a173ba7c8517c7d60c138e1a3f Mon Sep 17 00:00:00 2001 From: Anemy Date: Mon, 10 Jan 2022 11:06:41 -0500 Subject: [PATCH 3/9] Update how we show file input on tls tab --- .../tls-ssl-tab/tls-certificate-authority.tsx | 43 ++++++++----------- .../tls-ssl-tab/tls-ssl-tab.tsx | 1 - 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx index 73d3cfe3cf9..fe4d0413766 100644 --- a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx +++ b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx @@ -22,20 +22,12 @@ import FormFieldContainer from '../../form-field-container'; import { TLS_OPTIONS } from '../../../constants/ssl-tls-options'; const caFieldsContainer = css({ - // display: 'flex', - // flexDirection: 'row', - // alignItems: 'center', width: '50%', }); -// const caFileInputContainer = css({ -// flexGrow: 1 -// }); - -// const removeFileButtonStyles = css({ -// marginLeft: spacing[1], -// marginTop: spacing[1], -// }); +const removeFileButtonStyles = css({ + marginLeft: spacing[1], +}); function TLSCertificateAuthority({ connectionStringUrl, @@ -46,10 +38,7 @@ function TLSCertificateAuthority({ disabled: boolean; updateConnectionFormField: UpdateConnectionFormField; }): React.ReactElement { - const [caFile, setCAFile] = useState(undefined); - const [useCustomCA, setUseCustomCA] = useState( - connectionStringUrl.searchParams.get('tlsCAFile') !== null - ); + const [caFile, setCAFile] = useState(undefined); const onChangeTLSOption = useCallback( (event: React.ChangeEvent) => { @@ -63,9 +52,6 @@ function TLSCertificateAuthority({ return ( - {/*
*/} { - setCAFile(files); + onChange={(files: string[] | null) => { + setCAFile( + (files && files.length > 0) + ? files[0] + : undefined + ); // formFieldChanged(name as IdentityFormKeys, files[0]); }} - values={caFile} + // values={caFile} /> - {/*
*/} - {/* {caFile && ( + {caFile && ( +
+ {caFile} setCAFile(undefined) } > - + - )} */} - {/*
*/} + + )}
); } diff --git a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx index b45ae9f4ea8..9a6cdaa7541 100644 --- a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx +++ b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx @@ -178,7 +178,6 @@ function TLSTab({ {tlsOptionFields.map((tlsOptionField) => ( alert(`update ${tlsOptionField.name}`)} label={tlsOptionField.name} disabled={tlsOptionsDisabled} From d80322f9cdfe1dc32cf5757d19a2bfcd1e6002c7 Mon Sep 17 00:00:00 2001 From: Anemy Date: Mon, 10 Jan 2022 14:05:59 -0500 Subject: [PATCH 4/9] Use connection string values in tls ssl tab --- .../tls-ssl-tab/tls-certificate-authority.tsx | 41 ++++-------- .../tls-ssl-tab/tls-client-certificate.tsx | 62 ++++++++----------- .../tls-ssl-tab/tls-ssl-tab.tsx | 21 +++++-- 3 files changed, 55 insertions(+), 69 deletions(-) diff --git a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx index 4ee1b333103..45c94053c74 100644 --- a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx +++ b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-certificate-authority.tsx @@ -1,25 +1,15 @@ import { css } from '@emotion/css'; -import React, { useCallback, useMemo, useState } from 'react'; +import React from 'react'; import { - Checkbox, - Description, FileInput, Icon, IconButton, - Label, - RadioBox, - RadioBoxGroup, - Radio, - RadioGroup, - TextInput, spacing, } from '@mongodb-js/compass-components'; import ConnectionStringUrl from 'mongodb-connection-string-url'; -import type { MongoClientOptions } from 'mongodb'; +import { MongoClientOptions } from 'mongodb'; -import { UpdateConnectionFormField } from '../../../hooks/use-connect-form'; import FormFieldContainer from '../../form-field-container'; -import { TLS_OPTIONS } from '../../../constants/ssl-tls-options'; const caFieldsContainer = css({ width: '70%', @@ -32,23 +22,15 @@ const removeFileButtonStyles = css({ function TLSCertificateAuthority({ connectionStringUrl, disabled, - updateConnectionFormField, + updateCAFile, }: { connectionStringUrl: ConnectionStringUrl; disabled: boolean; - updateConnectionFormField: UpdateConnectionFormField; + updateCAFile: (newCAFile: string | null) => void; }): React.ReactElement { - const [caFile, setCAFile] = useState(undefined); - - const onChangeTLSOption = useCallback( - (event: React.ChangeEvent) => { - updateConnectionFormField({ - type: 'update-tls-option', - tlsOption: event.target.value as TLS_OPTIONS, - }); - }, - [updateConnectionFormField] - ); + const caFile = connectionStringUrl + .typedSearchParams() + .get('tlsCAFile'); return ( @@ -60,11 +42,8 @@ function TLSCertificateAuthority({ link={ 'https://docs.mongodb.com/manual/reference/connection-string/#mongodb-urioption-urioption.tlsCAFile' } - // id={name} - // dataTestId={name} onChange={(files: string[] | null) => { - setCAFile(files && files.length > 0 ? files[0] : undefined); - // formFieldChanged(name as IdentityFormKeys, files[0]); + updateCAFile(files && files.length > 0 ? files[0] : null); }} // values={caFile} /> @@ -74,7 +53,9 @@ function TLSCertificateAuthority({ setCAFile(undefined)} + onClick={() => { + updateCAFile(null); + }} > diff --git a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-client-certificate.tsx b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-client-certificate.tsx index ca402b2e086..29087573e7a 100644 --- a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-client-certificate.tsx +++ b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-client-certificate.tsx @@ -1,21 +1,9 @@ import { css } from '@emotion/css'; -import React, { useCallback, useMemo, useState } from 'react'; -import { - Checkbox, - Description, - FileInput, - Icon, - IconButton, - Label, - RadioBox, - RadioBoxGroup, - TextInput, - spacing, -} from '@mongodb-js/compass-components'; +import React from 'react'; +import { FileInput, TextInput } from '@mongodb-js/compass-components'; import ConnectionStringUrl from 'mongodb-connection-string-url'; -import type { MongoClientOptions } from 'mongodb'; +import { MongoClientOptions } from 'mongodb'; -import { UpdateConnectionFormField } from '../../../hooks/use-connect-form'; import FormFieldContainer from '../../form-field-container'; const inputFieldStyles = css({ @@ -25,17 +13,25 @@ const inputFieldStyles = css({ function TLSClientCertificate({ connectionStringUrl, disabled, - updateConnectionFormField, + updateTLSClientCertificate, + updateTLSClientCertificatePassword, }: { connectionStringUrl: ConnectionStringUrl; disabled: boolean; - updateConnectionFormField: UpdateConnectionFormField; + updateTLSClientCertificate: ( + newClientCertificateKeyFile: string | null + ) => void; + updateTLSClientCertificatePassword: (newPassword: string | null) => void; }): React.ReactElement { - const [useClientCert, setUseClientCert] = useState( - connectionStringUrl.searchParams.get('tlsCertificateKeyFile') !== null - ); // TODO: Override when underlying connection changes? + const typedParams = + connectionStringUrl.typedSearchParams(); + const clientCertificateKeyFile = typedParams.get('tlsCertificateKeyFile'); + const tlsCertificateKeyFilePassword = typedParams.get( + 'tlsCertificateKeyFilePassword' + ); + return ( <> @@ -47,34 +43,30 @@ function TLSClientCertificate({ link={ 'https://docs.mongodb.com/manual/reference/connection-string/#mongodb-urioption-urioption.tlsCertificateKeyFile' } + values={clientCertificateKeyFile ? [clientCertificateKeyFile] : []} // id={name} // dataTestId={name} onChange={(files: string[]) => { - alert(`client cert change ${files.join(',')}`); - // formFieldChanged(name as IdentityFormKeys, files[0]); + updateTLSClientCertificate( + files && files.length > 0 ? files[0] : null + ); }} /> ) => { - // formFieldChanged( - // name as IdentityFormKeys, - // name === 'port' ? Number(value) : value - // ); - // }} + onChange={({ + target: { value }, + }: React.ChangeEvent) => { + updateTLSClientCertificatePassword(value); + }} disabled={disabled} + id="tlsCertificateKeyFilePassword" label="Client Key Password" // https://docs.mongodb.com/manual/reference/connection-string/#mongodb-urioption-urioption.tlsCertificateKeyFilePassword type="password" - value={ - connectionStringUrl.searchParams.get( - 'tlsCertificateKeyFilePassword' - ) || '' - } + value={tlsCertificateKeyFilePassword || ''} /> diff --git a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx index ac96afdedf1..1bdcf516085 100644 --- a/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx +++ b/packages/connect-form/src/components/advanced-options-tabs/tls-ssl-tab/tls-ssl-tab.tsx @@ -112,7 +112,11 @@ function TLSTab({ [updateConnectionFormField] ); - const tlsOptionFields = [ + const tlsOptionFields: { + name: keyof MongoClientOptions; + description: string; + checked: boolean; + }[] = [ { name: 'tlsInsecure', description: @@ -185,18 +189,27 @@ function TLSTab({ { + handleFieldChanged('tlsCAFile', newCertificatePath); + }} /> { + handleFieldChanged('tlsCertificateKeyFile', newCertificatePath); + }} + updateTLSClientCertificatePassword={( + newCertificatePath: string | null + ) => { + handleFieldChanged('tls', newCertificatePath); + }} /> {tlsOptionFields.map((tlsOptionField) => ( ) => { - handleFieldChanged(tlsOptionField.name, event.target.value); + handleFieldChanged(tlsOptionField.name, event.target.checked); }} label={tlsOptionField.name} disabled={tlsOptionsDisabled} From 2273e68e460ae9d1e604e24263a30efeb00dc19e Mon Sep 17 00:00:00 2001 From: Anemy Date: Fri, 14 Jan 2022 13:42:18 -0500 Subject: [PATCH 5/9] Add tests for tls attributes --- .../src/components/file-input.spec.tsx | 85 ++++++- .../src/components/file-input.tsx | 37 ++- .../tls-ssl-tab/tls-certificate-authority.tsx | 29 +-- .../tls-ssl-tab/tls-client-certificate.tsx | 7 +- .../tls-ssl-tab/tls-ssl-tab.spec.tsx | 220 +++++++++++++++++- .../tls-ssl-tab/tls-ssl-tab.tsx | 9 +- 6 files changed, 349 insertions(+), 38 deletions(-) diff --git a/packages/compass-components/src/components/file-input.spec.tsx b/packages/compass-components/src/components/file-input.spec.tsx index f91576f650e..d0eb61cb83b 100644 --- a/packages/compass-components/src/components/file-input.spec.tsx +++ b/packages/compass-components/src/components/file-input.spec.tsx @@ -2,7 +2,13 @@ import React from 'react'; import sinon from 'sinon'; import { expect } from 'chai'; -import { render, screen, cleanup } from '@testing-library/react'; +import { + render, + screen, + cleanup, + fireEvent, + waitFor, +} from '@testing-library/react'; import FileInput, { Variant } from './file-input'; @@ -182,4 +188,81 @@ describe('FileInput', function () { const errorMessage = screen.getByTestId('file-input-error'); expect(errorMessage).to.exist; }); + + describe('when a file is chosen', function () { + beforeEach(async function () { + render( + + ); + + const fileInput = screen.getByTestId('test-file-input'); + + await waitFor(() => + fireEvent.change(fileInput, { + target: { + files: [ + { + path: 'new/file/path', + }, + ], + }, + }) + ); + }); + + it('calls onChange with the chosen file', function () { + expect(spy.callCount).to.equal(1); + expect(spy.firstCall.args[0]).to.deep.equal(['new/file/path']); + }); + }); + + it('renders the file name with close button when showFileOnNewLine=true', function () { + render( + + ); + + expect(screen.getByText('new/file/nice')).to.be.visible; + expect(screen.getByText('another/file/path')).to.be.visible; + expect(screen.getAllByLabelText('Remove file').length).to.equal(2); + }); + + describe('when a file is clicked to remove on multi line', function () { + beforeEach(async function () { + render( + + ); + + const removeButton = screen.getAllByLabelText('Remove file')[0]; + + await waitFor(() => fireEvent.click(removeButton)); + }); + + it('calls onChange with the file removed', function () { + expect(spy.callCount).to.equal(1); + expect(spy.firstCall.args[0]).to.deep.equal(['another/file/path']); + }); + }); }); diff --git a/packages/compass-components/src/components/file-input.tsx b/packages/compass-components/src/components/file-input.tsx index 70b85d3affc..eff7bbaa2f5 100644 --- a/packages/compass-components/src/components/file-input.tsx +++ b/packages/compass-components/src/components/file-input.tsx @@ -4,7 +4,7 @@ import { css, cx } from '@leafygreen-ui/emotion'; import { uiColors } from '@leafygreen-ui/palette'; import { spacing } from '@leafygreen-ui/tokens'; -import { Button, Icon, Label, Link, Description } from '..'; +import { Button, Icon, IconButton, Label, Link, Description } from '..'; const { base: redBaseColor } = uiColors.red; @@ -21,6 +21,15 @@ const formItemVerticalStyles = css({ margin: '5px auto 20px', }); +const removeFileLineStyles = css({ + display: 'flex', + flexDirection: 'row', +}); + +const removeFileButtonStyles = css({ + marginLeft: spacing[1], +}); + const formItemErrorStyles = css({ border: `1px solid ${redBaseColor}`, borderRadius: '5px', @@ -106,6 +115,7 @@ function FileInput({ error = false, errorMessage, variant = Variant.Horizontal, + showFileOnNewLine = false, link, description, values, @@ -122,6 +132,7 @@ function FileInput({ variant?: Variant; link?: string; description?: string; + showFileOnNewLine?: boolean; values?: string[]; labelAlignment?: 'right' | 'left' | 'center'; }): React.ReactElement { @@ -143,7 +154,7 @@ function FileInput({ }); onChange(files); }, - [onChange] + [onChange, values] ); const renderDescription = (): React.ReactElement | null => { @@ -230,6 +241,9 @@ function FileInput({ multiple={multi} onChange={onFilesChanged} style={{ display: 'none' }} + // Force a re-render when the values change so + // the component is controlled by the prop. + key={values ? values.join(',') : 'empty'} />