Skip to content

Commit

Permalink
Enhance OpenStack provider help text fields
Browse files Browse the repository at this point in the history
Reference: #646

Enhance and rephrase the help text fields and validations for following OpenStack dialogs:

1. Create OpenStack provider.
2. OpenStack Credentials view/edit modes.
3. OpenStack details - URL field
4. OpenStack details - URL icon help text

Changes include supporting formatted help text.

Signed-off-by: Sharon Gratch <[email protected]>
  • Loading branch information
sgratch committed Jan 30, 2024
1 parent ba41d31 commit 0b0c102
Show file tree
Hide file tree
Showing 11 changed files with 472 additions and 222 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { Trans } from 'react-i18next';
import { useForkliftTranslation } from 'src/utils/i18n';

import { ProviderModel } from '@kubev2v/types';
Expand All @@ -13,20 +14,65 @@ import { EditProviderURLModalProps } from './EditProviderURLModal';
export const OpenstackEditURLModal: React.FC<EditProviderURLModalProps> = (props) => {
const { t } = useForkliftTranslation();

const urlValidationHook: ValidationHookType = (value) => {
const isValidURL = validateURL(value.toString().trim());
const helperTextMsgs = {
error: (
<div className="forklift-edit-modal-field-error-validation">
<Trans t={t} ns="plugin__forklift-console-plugin">
Error: The format of the provided URL is invalid. Ensure the URL includes a scheme, a
domain name, and a path. For example:{' '}
<strong>https://identity_service.com:5000/v3</strong>.
</Trans>
</div>
),
warning: (
<div className="forklift--edit-modal-field-warning-validation">
<Trans t={t} ns="plugin__forklift-console-plugin">
Warning: The provided URL does not end with the API endpoint path:
<strong>
{'"'}/v3{'"'}
</strong>
{'. '}
Ensure the URL includes the correct path. For example:{' '}
<strong>https://identity_service.com:5000/v3</strong>.
</Trans>
</div>
),
success: (
<div className="forklift-edit-modal-field-success-validation">
<Trans t={t} ns="plugin__forklift-console-plugin">
For example: <strong>https://identity_service.com:5000/v3</strong>.
</Trans>
</div>
),
default: (
<div className="forklift-edit-modal-field-default-validation">
<Trans t={t} ns="plugin__forklift-console-plugin">
{'For example: <strong>https://identity_service.com:5000/v3</strong>.'}
</Trans>
</div>
),
};

return isValidURL
? {
validationHelpText: undefined,
validated: 'success',
}
: {
validationHelpText: t(
'URL must start with https:// or http:// and contain valid hostname and path',
),
validated: 'error',
};
const urlValidationHook: ValidationHookType = (value) => {
const trimmedUrl: string = value.toString().trim();
const isValidURL = validateURL(trimmedUrl);
// error
if (!isValidURL)
return {
validationHelpText: helperTextMsgs.error,
validated: 'error',
};
// warning
if (!trimmedUrl.endsWith('v3') && !trimmedUrl.endsWith('v3/'))
return {
validationHelpText: helperTextMsgs.warning,
validated: 'warning',
};
// success
return {
validationHelpText: helperTextMsgs.success,
validated: 'success',
};
};

return (
Expand All @@ -37,10 +83,8 @@ export const OpenstackEditURLModal: React.FC<EditProviderURLModalProps> = (props
label={props?.label || t('URL')}
model={ProviderModel}
variant={ModalVariant.large}
body={t(
'Specify OpenStack Identity (Keystone) endpoint, for example, http://controller:5000/v3.',
)}
helperText={t('Please enter URL for OpenStack services REST APIs.')}
body={t('URL of the OpenStack Identity (Keystone) endpoint.')}
helperText={helperTextMsgs.default}
onConfirmHook={patchProviderURL}
validationHook={urlValidationHook}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useCallback, useReducer } from 'react';
import { Trans } from 'react-i18next';
import { validateURL, Validation } from 'src/modules/Providers/utils';
import { useForkliftTranslation } from 'src/utils/i18n';

Expand All @@ -18,9 +19,51 @@ export const OpenstackProviderCreateForm: React.FC<OpenstackProviderCreateFormPr

const url = provider?.spec?.url || '';

const urlHelperTextMsgs = {
error: (
<div className="forklift--create-provider-field-error-validation">
<Trans t={t} ns="plugin__forklift-console-plugin">
Error: The format of the provided URL is invalid. Ensure the URL includes a scheme, a
domain name, and a path. For example:{' '}
<strong>https://identity_service.com:5000/v3</strong>.
</Trans>
</div>
),
warning: (
<div className="forklift--create-provider-field-warning-validation">
<Trans t={t} ns="plugin__forklift-console-plugin">
Warning: The provided URL does not end with the API endpoint path:{' '}
<strong>
{'"'}/v3{'"'}
</strong>
. Ensure the URL includes the correct path. For example:{' '}
<strong>https://identity_service.com:5000/v3</strong>.
</Trans>
</div>
),
success: (
<div className="forklift--create-provider-field-success-validation">
<Trans t={t} ns="plugin__forklift-console-plugin">
URL of the OpenStack Identity (Keystone) endpoint. For example:{' '}
<strong>https://identity_service.com:5000/v3</strong>.
</Trans>
</div>
),
default: (
<div className="forklift--create-provider-field-default-validation">
<Trans t={t} ns="plugin__forklift-console-plugin">
{
'URL of the OpenStack Identity (Keystone) endpoint. For example: <strong>https://identity_service.com:5000/v3</strong>.'
}
</Trans>
</div>
),
};

const initialState = {
validation: {
url: 'default' as Validation,
urlHelperText: urlHelperTextMsgs.default,
},
};

Expand All @@ -43,27 +86,44 @@ export const OpenstackProviderCreateForm: React.FC<OpenstackProviderCreateFormPr

const handleChange = useCallback(
(id, value) => {
const trimmedValue = value.trim();
if (id !== 'url') return;

const trimmedValue: string = value.trim();
const validationState = getURLValidationState(trimmedValue);

if (id === 'url') {
const validationState = validateURL(trimmedValue) ? 'success' : 'error';
dispatch({ type: 'SET_FIELD_VALIDATED', payload: { field: id, validationState } });
dispatch({
type: 'SET_FIELD_VALIDATED',
payload: { field: 'url', validationState },
});

onChange({ ...provider, spec: { ...provider.spec, url: trimmedValue } });
}
dispatch({
type: 'SET_FIELD_VALIDATED',
payload: {
field: 'urlHelperText',
validationState: urlHelperTextMsgs[validationState],
},
});

onChange({ ...provider, spec: { ...provider.spec, url: trimmedValue } });
},
[provider],
);

const getURLValidationState = (url: string): Validation => {
if (!validateURL(url)) return 'error';
if (!url.endsWith('v3') && !url.endsWith('v3/')) return 'warning';
return 'success';
};

return (
<Form isWidthLimited className="forklift-section-provider-edit">
<FormGroup
label={t('URL')}
isRequired
fieldId="url"
helperText={t('URL of the provider')}
helperText={state.validation.urlHelperText}
validated={state.validation.url}
helperTextInvalid={t('Error: URL is required and must be valid.')}
helperTextInvalid={state.validation.urlHelperText}
>
<TextInput
isRequired
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useCallback, useReducer } from 'react';
import { Trans } from 'react-i18next';
import { Base64 } from 'js-base64';
import {
openstackSecretFieldValidator,
Expand All @@ -7,7 +8,16 @@ import {
} from 'src/modules/Providers/utils';
import { useForkliftTranslation } from 'src/utils/i18n';

import { Divider, FileUpload, Form, FormGroup, Radio, Switch } from '@patternfly/react-core';
import {
Divider,
FileUpload,
Form,
FormGroup,
Popover,
Radio,
Switch,
} from '@patternfly/react-core';
import HelpIcon from '@patternfly/react-icons/dist/esm/icons/help-icon';

import { EditComponentProps } from '../BaseCredentialsSection';

Expand All @@ -22,6 +32,29 @@ import {
export const OpenstackCredentialsEdit: React.FC<EditComponentProps> = ({ secret, onChange }) => {
const { t } = useForkliftTranslation();

const insecureSkipVerifyHelperTextMsgs = {
error: t('Error: this field must be set to a boolean value.'),
successAndSkipped: t("The provider's CA certificate won't be validated."),
successAndNotSkipped: t("The provider's CA certificate will be validated."),
};

const cacertHelperTextMsgs = {
error: t(
'Error: The format of the provided CA Certificate is invalid. Ensure the CA certificate format is valid.',
),
success: t(
'A CA certificate to be trusted when connecting to the OpenStack Identity (Keystone) endpoint. Ensure the CA certificate format is valid. To use a CA certificate, drag the file to the text box or browse for it. To use the system CA certificates, leave the field empty.',
),
};

const insecureSkipVerifyHelperTextPopover = (
<Trans t={t} ns="plugin__forklift-console-plugin">
{
"Note: If 'Skip certificate validation' is selected, migrations from this provider will not be secure.<br><br> Insecure migration means that the transferred data is sent over an insecure connection and potentially sensitive data could be exposed."
}
</Trans>
);

const authType = safeBase64Decode(secret?.data?.authType || '');
const username = safeBase64Decode(secret?.data?.username || '');
const insecureSkipVerify = safeBase64Decode(secret?.data?.insecureSkipVerify || '') === 'true';
Expand Down Expand Up @@ -62,6 +95,7 @@ export const OpenstackCredentialsEdit: React.FC<EditComponentProps> = ({ secret,
authenticationType: authenticationType,
validation: {
cacert: 'default' as Validation,
insecureSkipVerify: 'default' as Validation,
},
};

Expand Down Expand Up @@ -151,7 +185,9 @@ export const OpenstackCredentialsEdit: React.FC<EditComponentProps> = ({ secret,
role="radiogroup"
fieldId="authType"
label={t('Authentication type')}
helperText={t('Type of authentication to use when connecting to OpenStack REST API.')}
helperText={t(
'Method of authentication to use when connecting to the OpenStack Identity (Keystone) server.',
)}
>
<Radio
name="authType"
Expand Down Expand Up @@ -212,6 +248,21 @@ export const OpenstackCredentialsEdit: React.FC<EditComponentProps> = ({ secret,

<FormGroup
label={t('Skip certificate validation')}
labelIcon={
<Popover
headerContent={<div>Skip certificate validation</div>}
bodyContent={<div>{insecureSkipVerifyHelperTextPopover}</div>}
alertSeverityVariant="info"
>
<button
type="button"
onClick={(e) => e.preventDefault()}
className="pf-c-form__group-label-help"
>
<HelpIcon noVerticalAlign />
</button>
</Popover>
}
fieldId="insecureSkipVerify"
validated={state.validation.insecureSkipVerify}
helperTextInvalid={t('Error: Insecure Skip Verify must be a boolean value.')}
Expand All @@ -220,9 +271,9 @@ export const OpenstackCredentialsEdit: React.FC<EditComponentProps> = ({ secret,
className="forklift-section-secret-edit-switch"
id="insecureSkipVerify"
name="insecureSkipVerify"
label={t("The provider's REST API TLS certificate won't be validated.")}
labelOff={t("The provider's REST API TLS certificate will be validated.")}
aria-label={t("If true, the provider's REST API TLS certificate won't be validated.")}
label={insecureSkipVerifyHelperTextMsgs.successAndSkipped}
labelOff={insecureSkipVerifyHelperTextMsgs.successAndNotSkipped}
aria-label={insecureSkipVerifyHelperTextMsgs.successAndSkipped}
isChecked={insecureSkipVerify}
hasCheckIcon
onChange={(value) => handleChange('insecureSkipVerify', value ? 'true' : 'false')}
Expand All @@ -231,15 +282,15 @@ export const OpenstackCredentialsEdit: React.FC<EditComponentProps> = ({ secret,
<FormGroup
label={
insecureSkipVerify
? t('CA certificate - disabled when skip certificate validation is checked')
: t('CA certificate - leave empty to use system certificates')
? t(
"CA certificate - disabled and ignored when 'Skip certificate validation' is checked",
)
: t('CA certificate - leave empty to use system CA certificates')
}
fieldId="cacert"
helperText={t(
'Custom certification used to verify the OpenStack REST API server, when empty use system certificate.',
)}
helperText={cacertHelperTextMsgs.success}
validated={state.validation.cacert}
helperTextInvalid={t('Error: CA Certificate must be valid.')}
helperTextInvalid={cacertHelperTextMsgs.error}
>
<FileUpload
id="cacert"
Expand Down
Loading

0 comments on commit 0b0c102

Please sign in to comment.