Skip to content

Commit

Permalink
feat(sap-features-hub): add pre-installation form step5
Browse files Browse the repository at this point in the history
ref: MANAGER-15979

Signed-off-by: Paul Dickerson <[email protected]>
  • Loading branch information
Paul Dickerson committed Jan 24, 2025
1 parent 0f90a4f commit 000674f
Show file tree
Hide file tree
Showing 13 changed files with 244 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,28 @@
"system_password_sap_master_helper": "Sera utilisé pour tous les utilisateurs SAP demandés par l'installeur (DDIC, sapadm, etc.)",
"system_cta": "Saisissez l'emplacement des sources SAP",
"source_title": "Fournissez les sources d'installation SAP",
"source_subtitle": "Saisissez les informations de votre conteneur Object Storage S3 OVHcloud où se trouvent vos sources d'installation SAP.",
"source_subtitle": "Saisissez les informations de votre conteneur Object Storage OVHcloud où se trouvent vos sources d'installation SAP.",
"source_input_container": "ID du conteneur",
"source_helper_container": "Le nom du conteneur doit comporter entre 3 et 63 caractères, commencer et se terminer par une lettre ou un chiffre en minuscule (de a à z, de 0 à 9), et peut inclure les signes de ponctuation « . » et « - ».",
"source_helper_container": "Le nom du conteneur doit comporter entre 3 et 63 caractères, commencer et se terminer par une lettre en minuscule ou un chiffre (de a à z, de 0 à 9), et peut inclure les signes de ponctuation « . » et « - ».",
"source_helper_endpoint": "L'endpoint est au format URL.",
"source_input_access_key": "Clé d'accès",
"source_helper_access_key": "Une clé d'accès est composée de 32 caractères alphanumériques.",
"source_input_secret_key": "Clé secrète",
"source_helper_secret_key": "Une clé secrète est composée de 32 caractères alphanumériques.",
"source_cta": "Spécifiez la configuration OS",
"source_error_check": "Erreur concernant les informations de conteneur fournies : {{error}}"
"source_error_check": "Erreur concernant les informations de conteneur fournies : {{error}}",
"os_config_title": "Spécifiez les configurations OS",
"os_config_subtitle": "Saisissez les informations et configurations optionnelles de votre OS correspondant à vos besoins",
"os_config_input_domain": "Nom de domaine",
"os_config_helper_domain": "Le nom de domaine ne doit pas commencer, ni terminer par un point « . ».",
"os_config_input_suse": "Clé de licence SUSE",
"os_config_helper_suse": "La clé doit être au format alphanumérique et comprendre entre 16 et 32 caractères",
"os_config_toggle_update": "Réaliser une mise à jour des paquets OS avant l'installation",
"os_config_toggle_firewall_service": "Activer le firewall pour les services centraux",
"os_config_tooltip_firewall_service": "Autorise toutes les communications sur la SCS en provenance des autres serveurs de ce système SAP",
"os_config_toggle_firewall_server": "Activer le firewall pour les serveurs d'application",
"os_config_tooltip_firewall_server": "Autorise toutes les communications sur les serveurs d'application en provenance des autres serveurs de ce système SAP",
"os_config_toggle_firewall_database": "Activer le firewall pour la base de données SAP HANA",
"os_config_tooltip_firewall_database": "Autorise toutes les communications sur la base de données SAP HANA en provenance des autres serveurs de ce système SAP",
"os_config_cta": "Personnalisez vos machines virtuelles"
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import {
OdsText,
} from '@ovhcloud/ods-components/react';
import { OdsInputChangeEvent, OdsInputType } from '@ovhcloud/ods-components';
import { FormKey } from '@/types/form.type';

export type TextFieldProps = {
name: string;
name: FormKey;
value: string;
onOdsChange: (e: OdsInputChangeEvent) => void;
type?: OdsInputType;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import {
OdsFormField,
OdsText,
OdsToggle,
OdsTooltip,
} from '@ovhcloud/ods-components/react';
import { OdsToggleChangeEvent } from '@ovhcloud/ods-components';
import { FormKey } from '@/types/form.type';

export type ToggleFieldProps = {
name: FormKey;
checked: boolean;
onOdsChange: (e: OdsToggleChangeEvent) => void;
label?: string;
tooltip?: string;
};

export const ToggleField = ({
name,
checked,
onOdsChange,
label,
tooltip,
}: ToggleFieldProps) => {
return (
<OdsFormField
key={name}
className="w-full max-w-md flex flex-row items-center gap-x-2"
>
{label && (
<label htmlFor={`${name}-toggle`} slot="label">
<OdsText>{label}</OdsText>
</label>
)}
<OdsToggle
id={`${name}-toggle`}
name={name}
value={checked}
onOdsChange={onOdsChange}
className="ml-auto"
/>
{tooltip && (
<OdsTooltip triggerId={`${name}-toggle`}>{tooltip}</OdsTooltip>
)}
</OdsFormField>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const installationInitialValues: InstallationFormValues = {
endpoint: '',
accessKey: '',
secretKey: '',
domainName: '',
osLicense: '',
osUpdate: false,
firewallService: false,
firewallServer: false,
firewallDatabase: false,
} as const;

export const installationInitialErrors: InstallationFormErrors = Object.keys(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import InstallationInitialStep from '../initialStep/InstallationInitialStep.page
import InstallationStepDeployment from '../stepDeployment/InstallationStepDeployment.page';
import InstallationStepSystemInformation from '../stepSystemInformation/InstallationStepSystemInformation.page';
import InstallationStepSourceInformation from '../stepSourceInformation/InstallationStepSourceInformation.page';
import InstallationStepOSConfig from '../stepOSConfig/InstallationStepOSConfig.page';

const steps: Record<string, ReactNode> = {
'1': <InstallationInitialStep />,
'2': <InstallationStepDeployment />,
'3': <InstallationStepSystemInformation />,
'4': <InstallationStepSourceInformation />,
'5': <InstallationStepOSConfig />,
} as const;

export default function FormStep() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { OdsToggleChangeEvent } from '@ovhcloud/ods-components';
import { useFormSteps } from '@/hooks/formStep/useFormSteps';
import { useInstallationFormContext } from '@/context/InstallationForm.context';
import { TextField } from '@/components/Form/TextField.component';
import { getOSConfigFormData } from '@/utils/formStepData';
import FormLayout from '@/components/Form/FormLayout.component';
import {
OS_LICENCE_MAX_LENGTH,
OS_LICENCE_MIN_LENGTH,
OS_LICENSE_PATTERN,
} from './installationStepOSConfig.constants';
import { isValidDomain } from '@/utils/formValidation';
import { ToggleField } from '@/components/Form/ToggleField.component';

export default function InstallationStepOSConfig() {
const { t } = useTranslation('installation');
const { previousStep, nextStep } = useFormSteps();
const {
values: formValues,
errors: formErrors,
setValues,
setErrors,
} = useInstallationFormContext();

const { values, errors } = getOSConfigFormData({
values: formValues,
errors: formErrors,
});

const isStepValid = useMemo(
() => values.domainName && !errors.domainName && !errors.osLicense,
[values.domainName, errors.domainName, errors.osLicense],
);

const handleToggle = (e: OdsToggleChangeEvent) => {
const { name, value } = e.detail;
setValues((val) => ({ ...val, [name]: value }));
};

return (
<FormLayout
title={t('os_config_title')}
subtitle={t('os_config_subtitle')}
submitLabel={t('os_config_cta')}
isSubmitDisabled={!isStepValid}
onClickSubmit={nextStep}
onClickPrevious={previousStep}
>
<TextField
name="domainName"
label={t('os_config_input_domain')}
onOdsChange={(e) => {
const { name, value } = e.detail;
const isValid = isValidDomain(value as string);
setValues((val) => ({ ...val, [name]: value }));
setErrors((err) => ({
...err,
[name]: isValid ? '' : t('os_config_helper_domain'),
}));
}}
value={values.domainName}
error={values.domainName && errors.domainName}
placeholder="mydomain.local"
helperText={t('os_config_helper_domain')}
isRequired
/>
<TextField
name="osLicense"
label={t('os_config_input_suse')}
onOdsChange={(e) => {
const { name, value } = e.detail;
const isValid = e.detail.validity?.valid;
setValues((val) => ({ ...val, [name]: value }));
setErrors((err) => ({
...err,
[name]: !!value && !isValid ? t('os_config_helper_suse') : '',
}));
}}
value={values.osLicense}
error={values.osLicense && errors.osLicense}
placeholder="123456789ABCDEFG"
helperText={t('os_config_helper_suse')}
pattern={OS_LICENSE_PATTERN}
minlength={OS_LICENCE_MIN_LENGTH}
maxlength={OS_LICENCE_MAX_LENGTH}
/>
<ToggleField
name={'osUpdate'}
checked={values.osUpdate}
onOdsChange={handleToggle}
label={t('os_config_toggle_update')}
/>
<ToggleField
name={'firewallService'}
checked={values.firewallService}
onOdsChange={handleToggle}
label={t('os_config_toggle_firewall_service')}
tooltip={t('os_config_tooltip_firewall_service')}
/>
<ToggleField
name={'firewallServer'}
checked={values.firewallServer}
onOdsChange={handleToggle}
label={t('os_config_toggle_firewall_server')}
tooltip={t('os_config_tooltip_firewall_server')}
/>
<ToggleField
name={'firewallDatabase'}
checked={values.firewallDatabase}
onOdsChange={handleToggle}
label={t('os_config_toggle_firewall_database')}
tooltip={t('os_config_tooltip_firewall_database')}
/>
</FormLayout>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const OS_LICENSE_PATTERN = /^[A-Za-z0-9]{16,32}$/.source;
export const OS_LICENCE_MIN_LENGTH = 16;
export const OS_LICENCE_MAX_LENGTH = 32;
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
SOURCE_BUCKET_MIN_LENGTH,
SOURCE_KEY_LENGTH,
} from './installationStepSourceInformation.constants';
import { getSourceFormData } from '@/utils/formStep';
import { getSourceFormData } from '@/utils/formStepData';
import { isValidUrl } from '@/utils/formValidation';
import FormLayout from '@/components/Form/FormLayout.component';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from './installationStepSystemInformation.constants';
import { useInstallationFormContext } from '@/context/InstallationForm.context';
import { TextField } from '@/components/Form/TextField.component';
import { getSystemFormData } from '@/utils/formStep';
import { getSystemFormData } from '@/utils/formStepData';
import { isValidSapPassword } from '@/utils/formValidation';
import FormLayout from '@/components/Form/FormLayout.component';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { SystemFormKeys, TextInputData } from '@/types/form.type';
import { SystemForm, TextInputData } from '@/types/form.type';

// Un SID SAP comporte 3 caractères alphanumériques en majuscule et commence par une lettre.
const SAP_SID_PATTERN = /^[A-Z][A-Z0-9]{2}$/.source;

export const FORM_SAP_SIDS_LABEL = 'SAP SIDs';
export const SYSTEM_TEXT_INPUTS: TextInputData<SystemFormKeys>[] = [
export const SYSTEM_TEXT_INPUTS: TextInputData<keyof SystemForm>[] = [
{
name: 'sapSid',
label: 'SAP SID',
Expand All @@ -23,7 +23,7 @@ export const SYSTEM_TEXT_INPUTS: TextInputData<SystemFormKeys>[] = [
},
] as const;

export const SYSTEM_PASSWORD_INPUTS: TextInputData<SystemFormKeys>[] = [
export const SYSTEM_PASSWORD_INPUTS: TextInputData<keyof SystemForm>[] = [
{
name: 'masterSapPassword',
label: 'SAP MASTER',
Expand Down
13 changes: 10 additions & 3 deletions packages/manager/apps/sap-features-hub/src/types/form.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { TextFieldProps } from '@/components/Form/TextField.component';
type InstallationForm = InitializationForm &
DeploymentForm &
SystemForm &
SourceForm;
SourceForm &
OSConfigForm;
export type FormKey = keyof InstallationForm;

export type InstallationFormErrors = Record<FormKey, string>;
Expand Down Expand Up @@ -43,5 +44,11 @@ export type SourceForm = {
accessKey: string;
secretKey: string;
};

export type SystemFormKeys = keyof SystemForm;
export type OSConfigForm = {
domainName: string;
osLicense: string;
osUpdate: boolean;
firewallService: boolean;
firewallServer: boolean;
firewallDatabase: boolean;
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ import {
InstallationFormErrors,
SystemForm,
SourceForm,
OSConfigForm,
} from '@/types/form.type';

type FormStepData<T> = {
values: T;
errors: Record<keyof T, string>;
};

export const getSystemFormData = (
form: InstallationFormValues | InstallationFormErrors,
): SystemForm => ({
Expand All @@ -24,3 +30,26 @@ export const getSourceFormData = (
accessKey: form.accessKey,
secretKey: form.secretKey,
});

export const getOSConfigFormData = ({
values,
errors,
}: FormStepData<InstallationFormValues>): FormStepData<OSConfigForm> => {
const osConfigKeys: Array<keyof OSConfigForm> = [
'domainName',
'osLicense',
'osUpdate',
'firewallService',
'firewallServer',
'firewallDatabase',
];

return osConfigKeys.reduce(
(form, key) => ({
...form,
values: { ...form.values, [key]: values[key] ?? null },
errors: { ...form.errors, [key]: errors[key] ?? '' },
}),
{ values: {}, errors: {} } as FormStepData<OSConfigForm>,
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export const isValidUrl = (value: string) => {
}
};

export const isValidDomain = (value: string) => {
return /^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\.)+[A-Za-z]{2,63}$/.test(value);
};

export const isValidSapPassword = (password: string): boolean => {
if (password?.length < 8) return false;
if (!/[a-z]/.test(password)) return false;
Expand Down

0 comments on commit 000674f

Please sign in to comment.