From cc638998dbaac46d9ff5fde2f53344377025f63b Mon Sep 17 00:00:00 2001 From: Simon Chaumet Date: Tue, 3 Dec 2024 18:03:51 +0100 Subject: [PATCH 1/3] feat(pci-block-storage): add availability zone step ref: TAPC-2111 Signed-off-by: Simon Chaumet --- .../apps/pci-block-storage/package.json | 2 +- .../translations/add/Messages_fr_FR.json | 1 + .../src/api/data/availableVolumes.ts | 14 ++ .../pci-block-storage/src/api/data/volume.ts | 1 + .../src/pages/new/New.page.tsx | 35 ++++- .../new/components/AvailabilityZoneStep.tsx | 70 ++++++++++ .../new/components/LocationStep.component.tsx | 28 +++- .../src/pages/new/form.type.ts | 1 + .../src/pages/new/hooks/useStep.ts | 21 ++- .../src/pages/new/hooks/useVolumeStepper.ts | 129 ++++++++++++++---- yarn.lock | 10 ++ 11 files changed, 274 insertions(+), 38 deletions(-) create mode 100644 packages/manager/apps/pci-block-storage/src/pages/new/components/AvailabilityZoneStep.tsx diff --git a/packages/manager/apps/pci-block-storage/package.json b/packages/manager/apps/pci-block-storage/package.json index e94e666f5e7c..8d8690a9fcc4 100644 --- a/packages/manager/apps/pci-block-storage/package.json +++ b/packages/manager/apps/pci-block-storage/package.json @@ -17,7 +17,7 @@ "dependencies": { "@ovh-ux/manager-config": "^8.0.2", "@ovh-ux/manager-core-api": "^0.9.0", - "@ovh-ux/manager-pci-common": "^0.14.4", + "@ovh-ux/manager-pci-common": "^0.15.0", "@ovh-ux/manager-react-components": "^1.43.1", "@ovh-ux/manager-react-core-application": "^0.11.5", "@ovh-ux/manager-react-shell-client": "^0.8.5", diff --git a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_fr_FR.json b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_fr_FR.json index 1081791c6f9e..2bdfa71cbe03 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_fr_FR.json +++ b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_fr_FR.json @@ -11,6 +11,7 @@ "pci_projects_project_storages_blocks_add_type_addon_iops_not_guaranteed": "Jusqu'à {{iops}} IOPS{{separator}} ", "pci_projects_project_storages_blocks_add_type_addon_capacity_max": "{{capacity}} max.", "pci_projects_project_storages_blocks_add_type_addon_price": "{{price}} HT/Go/heure", + "pci_projects_project_storages_blocks_add_availability_zone": "Sélectionnez une zone de disponibilité", "pci_projects_project_storages_blocks_add_size_title": "Capacité du volume", "pci_projects_project_storages_blocks_add_size_help": "La taille maximale dépend de votre quota disponible.", "pci_projects_project_storages_blocks_add_size_unit": "Go", diff --git a/packages/manager/apps/pci-block-storage/src/api/data/availableVolumes.ts b/packages/manager/apps/pci-block-storage/src/api/data/availableVolumes.ts index 443995c41db5..bae1be8ff819 100644 --- a/packages/manager/apps/pci-block-storage/src/api/data/availableVolumes.ts +++ b/packages/manager/apps/pci-block-storage/src/api/data/availableVolumes.ts @@ -1,4 +1,5 @@ import { v6 } from '@ovh-ux/manager-core-api'; +import { TLocalisation } from '@/api/hooks/useRegions'; export type TAvailableVolumesResponse = { plans: { @@ -6,6 +7,7 @@ export type TAvailableVolumesResponse = { regions: { name: string; enabled: boolean; + type: string; }[]; }[]; }; @@ -20,3 +22,15 @@ export const getProjectsAvailableVolumes = async ( return data; }; + +export function isRegionWith3AZ(region: Pick) { + return region.type === 'region-3-az'; +} + +/** + * TODO: use real informations + * @param planCode + */ +export function isProductWithAvailabilityZone(planCode: string) { + return planCode.startsWith('volume.high-speed'); +} diff --git a/packages/manager/apps/pci-block-storage/src/api/data/volume.ts b/packages/manager/apps/pci-block-storage/src/api/data/volume.ts index 58406f5c13f3..7e2e0d2beb38 100644 --- a/packages/manager/apps/pci-block-storage/src/api/data/volume.ts +++ b/packages/manager/apps/pci-block-storage/src/api/data/volume.ts @@ -185,6 +185,7 @@ export interface AddVolumeProps { regionName: string; volumeCapacity: number; volumeType: string; + availabilityZone?: string; } export const addVolume = async ({ diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/New.page.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/New.page.tsx index 65480db49d69..ddd1cea561b6 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/New.page.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/new/New.page.tsx @@ -23,6 +23,7 @@ import { LocationStep } from './components/LocationStep.component'; import { useVolumeStepper } from './hooks/useVolumeStepper'; import { useAddVolume } from '@/api/hooks/useVolume'; import { ExtenBannerBeta } from '@/components/exten-banner-beta/ExtenBannerBeta'; +import { AvailabilityZoneStep } from '@/pages/new/components/AvailabilityZoneStep'; export default function NewPage(): JSX.Element { const { t } = useTranslation('common'); @@ -35,7 +36,7 @@ export default function NewPage(): JSX.Element { const backHref = useHref('..'); const isDiscovery = isDiscoveryProject(project); const { addError, addSuccess, clearNotifications } = useNotifications(); - const stepper = useVolumeStepper(); + const stepper = useVolumeStepper(projectId); const { addVolume } = useAddVolume({ projectId, @@ -109,7 +110,7 @@ export default function NewPage(): JSX.Element {
+ {stepper.availabilityZone.step.isShown && ( + + {!!stepper.form.region?.name && ( + + )} + + )} diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/components/AvailabilityZoneStep.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/components/AvailabilityZoneStep.tsx new file mode 100644 index 000000000000..ad67d99e879f --- /dev/null +++ b/packages/manager/apps/pci-block-storage/src/pages/new/components/AvailabilityZoneStep.tsx @@ -0,0 +1,70 @@ +import { TilesInputComponent } from '@ovh-ux/manager-react-components'; +import { useMemo, useState } from 'react'; +import { OsdsButton, OsdsText } from '@ovhcloud/ods-components/react'; +import { ODS_BUTTON_SIZE } from '@ovhcloud/ods-components'; +import { + ODS_THEME_COLOR_INTENT, + ODS_THEME_TYPOGRAPHY_LEVEL, + ODS_THEME_TYPOGRAPHY_SIZE, +} from '@ovhcloud/ods-common-theming'; +import { useTranslation } from 'react-i18next'; +import { Step } from '@/pages/new/hooks/useStep'; + +type Props = { + regionName: string; + step: Step; + onSubmit: (zone: string) => void; +}; + +export function AvailabilityZoneStep({ regionName, step, onSubmit }: Props) { + const { t } = useTranslation('stepper'); + + // TODO: use real informations + const zones = useMemo( + () => + ['a', 'b', 'c'].map((suffix) => `${regionName.toLowerCase()}-${suffix}`), + [regionName], + ); + const [selectedZone, setSelectedZone] = useState( + undefined, + ); + const displayedZones = useMemo( + () => (!!selectedZone && step.isLocked ? [selectedZone] : zones), + [zones, selectedZone, step], + ); + + return ( +
+ + items={displayedZones} + value={selectedZone} + onInput={(z) => setSelectedZone(z)} + label={(z) => ( +
+
+ + {z} + +
+
+ )} + /> + {!!selectedZone && !step.isLocked && ( +
+ onSubmit(selectedZone)} + className="w-fit" + > + {t('common_stepper_next_button_label')} + +
+ )} +
+ ); +} diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/components/LocationStep.component.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/components/LocationStep.component.tsx index ddca42059db0..eeecacc71618 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/components/LocationStep.component.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/new/components/LocationStep.component.tsx @@ -8,9 +8,13 @@ import { useProject, RegionSelector, RegionSummary, + usePCICommonContextFactory, + PCICommonContext, } from '@ovh-ux/manager-pci-common'; import { TLocalisation } from '@/api/hooks/useRegions'; import { StepState } from '@/pages/new/hooks/useStep'; +import { useProjectsAvailableVolumes } from '@/api/hooks/useProjectsAvailableVolumes'; +import { isRegionWith3AZ } from '@/api/data/availableVolumes'; interface LocationProps { projectId: string; @@ -18,6 +22,19 @@ interface LocationProps { onSubmit: (region: TLocalisation) => void; } +const useHas3AZRegion = (projectId: string) => { + const { data: availableVolumes, isPending } = useProjectsAvailableVolumes( + projectId, + ); + + return { + has3AZ: + availableVolumes?.plans.some((p) => p.regions.some(isRegionWith3AZ)) || + false, + isPending, + }; +}; + export function LocationStep({ projectId, step, @@ -28,8 +45,12 @@ export function LocationStep({ const { data: project } = useProject(); const isDiscovery = isDiscoveryProject(project); const hasRegion = !!region; + + const { has3AZ } = useHas3AZRegion(projectId); + const pciCommonProperties = usePCICommonContextFactory({ has3AZ }); + return ( - <> + {hasRegion && step.isLocked && } {(!step.isLocked || isDiscovery) && ( r.isMacro || - r.services.some((s) => s.name === 'volume' && s.status === 'UP') + r.services.some((s) => s.name === 'volume' && s.status === 'UP') || + r.type === 'region-3-az' } /> )} @@ -51,6 +73,6 @@ export function LocationStep({ {tStepper('common_stepper_next_button_label')} )} - + ); } diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/form.type.ts b/packages/manager/apps/pci-block-storage/src/pages/new/form.type.ts index b8364cc91548..2752ed1ee007 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/form.type.ts +++ b/packages/manager/apps/pci-block-storage/src/pages/new/form.type.ts @@ -6,4 +6,5 @@ export type TFormState = { volumeType: TAddon; volumeName: string; volumeCapacity: number; + availabilityZone: string; }; diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/hooks/useStep.ts b/packages/manager/apps/pci-block-storage/src/pages/new/hooks/useStep.ts index f777b8611be8..57276714d09f 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/hooks/useStep.ts +++ b/packages/manager/apps/pci-block-storage/src/pages/new/hooks/useStep.ts @@ -4,21 +4,40 @@ export interface StepState { isOpen: boolean; isChecked: boolean; isLocked: boolean; + isShown: boolean; } -export function useStep(initialState?: Readonly>) { +export type Step = StepState & { + open: () => void; + close: () => void; + check: () => void; + uncheck: () => void; + lock: () => void; + unlock: () => void; + show: () => void; + hide: () => void; +}; + +export function useStep(initialState?: Readonly>): Step { const [isOpen, setIsOpen] = useState(!!initialState?.isOpen); const [isChecked, setIsChecked] = useState(!!initialState?.isChecked); const [isLocked, setIsLocked] = useState(!!initialState?.isLocked); + const [isShown, setIsShown] = useState( + initialState && 'isShown' in initialState ? initialState.isShown : true, + ); + return { isOpen, isChecked, isLocked, + isShown, open: () => setIsOpen(true), close: () => setIsOpen(false), check: () => setIsChecked(true), uncheck: () => setIsChecked(false), lock: () => setIsLocked(true), unlock: () => setIsLocked(false), + show: () => setIsShown(true), + hide: () => setIsShown(false), }; } diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/hooks/useVolumeStepper.ts b/packages/manager/apps/pci-block-storage/src/pages/new/hooks/useVolumeStepper.ts index dbd2b0c219e7..05c3b89f698a 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/hooks/useVolumeStepper.ts +++ b/packages/manager/apps/pci-block-storage/src/pages/new/hooks/useVolumeStepper.ts @@ -1,45 +1,74 @@ -import { useState } from 'react'; -import { TAddon } from '@ovh-ux/manager-pci-common'; -import { useStep } from '@/pages/new/hooks/useStep'; +import { useEffect, useMemo, useState } from 'react'; +import { TAddon, useProjectRegions } from '@ovh-ux/manager-pci-common'; +import { Step, useStep } from '@/pages/new/hooks/useStep'; import { TFormState } from '@/pages/new/form.type'; import { TLocalisation } from '@/api/hooks/useRegions'; +import { + isProductWithAvailabilityZone, + isRegionWith3AZ, +} from '@/api/data/availableVolumes'; -export const DEFAULT_FORM_STATE: TFormState = { - region: undefined, - volumeType: undefined, - volumeName: '', - volumeCapacity: 10, -}; +export function useVolumeStepper(projectId: string) { + const { data } = useProjectRegions(projectId); + const is3AZAvailable = useMemo(() => !!data && data.some(isRegionWith3AZ), [ + data, + ]); -export function useVolumeStepper() { - const [form, setForm] = useState({ - ...DEFAULT_FORM_STATE, - }); + const [form, setForm] = useState>({}); const locationStep = useStep({ isOpen: true }); const volumeTypeStep = useStep(); + const availabilityZoneStep = useStep(); const capacityStep = useStep(); const volumeNameStep = useStep(); const validationStep = useStep(); + useEffect(() => { + if (is3AZAvailable) { + availabilityZoneStep.show(); + } else { + availabilityZoneStep.hide(); + } + }, [is3AZAvailable]); + + const order = [ + locationStep, + volumeTypeStep, + availabilityZoneStep, + capacityStep, + volumeNameStep, + validationStep, + ]; + return { form, location: { step: locationStep, edit: () => { locationStep.unlock(); - [volumeTypeStep, capacityStep, volumeNameStep, validationStep].forEach( - (step) => { - step.uncheck(); - step.unlock(); - step.close(); - }, - ); - setForm({ ...DEFAULT_FORM_STATE }); + [ + volumeTypeStep, + availabilityZoneStep, + capacityStep, + volumeNameStep, + validationStep, + ].forEach((step) => { + step.uncheck(); + step.unlock(); + step.close(); + }); + setForm({}); }, submit: (region: TLocalisation) => { locationStep.check(); locationStep.lock(); volumeTypeStep.open(); + if (is3AZAvailable) { + if (isRegionWith3AZ(region)) { + availabilityZoneStep.show(); + } else { + availabilityZoneStep.hide(); + } + } setForm((f) => ({ ...f, region, @@ -50,23 +79,64 @@ export function useVolumeStepper() { step: volumeTypeStep, edit: () => { volumeTypeStep.unlock(); - [capacityStep, volumeNameStep, validationStep].forEach((step) => { + [ + availabilityZoneStep, + capacityStep, + volumeNameStep, + validationStep, + ].forEach((step) => { step.uncheck(); step.unlock(); step.close(); }); - setForm((f) => ({ ...DEFAULT_FORM_STATE, region: f.region })); + setForm((f) => ({ region: f.region })); }, submit: (volumeType: TAddon) => { volumeTypeStep.check(); volumeTypeStep.lock(); - capacityStep.open(); + if ( + is3AZAvailable && + isRegionWith3AZ(form.region) && + isProductWithAvailabilityZone(volumeType.planCode) + ) { + availabilityZoneStep.show(); + availabilityZoneStep.open(); + } else { + availabilityZoneStep.hide(); + capacityStep.open(); + } setForm((f) => ({ ...f, volumeType, })); }, }, + availabilityZone: { + step: availabilityZoneStep, + edit: () => { + volumeTypeStep.unlock(); + [ + availabilityZoneStep, + capacityStep, + volumeNameStep, + validationStep, + ].forEach((step) => { + step.uncheck(); + step.unlock(); + step.close(); + }); + setForm((f) => ({ region: f.region, volumeType: f.volumeType })); + }, + submit: (availabilityZone: string) => { + availabilityZoneStep.check(); + availabilityZoneStep.lock(); + capacityStep.open(); + setForm((f) => ({ + ...f, + availabilityZone, + })); + }, + }, capacity: { step: capacityStep, edit: () => { @@ -77,9 +147,9 @@ export function useVolumeStepper() { step.close(); }); setForm((f) => ({ - ...DEFAULT_FORM_STATE, region: f.region, volumeType: f.volumeType, + availabilityZone: f.availabilityZone, })); }, submit: (volumeCapacity: number) => { @@ -100,8 +170,10 @@ export function useVolumeStepper() { validationStep.unlock(); validationStep.close(); setForm((f) => ({ - ...f, - volumeName: DEFAULT_FORM_STATE.volumeName, + region: f.region, + volumeType: f.volumeType, + availabilityZone: f.availabilityZone, + volumeCapacity: f.volumeCapacity, })); }, submit: (volumeName: string) => { @@ -120,5 +192,8 @@ export function useVolumeStepper() { validationStep.lock(); }, }, + getOrder(step: Step) { + return order.filter((o) => o.isShown).findIndex((o) => o === step) + 1; + }, }; } diff --git a/yarn.lock b/yarn.lock index 003f1f4df7f2..c3b1cf8d243d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5931,6 +5931,16 @@ date-fns "^3.6.0" lodash.isequal "^4.5.0" +"@ovh-ux/manager-pci-common@^0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@ovh-ux/manager-pci-common/-/manager-pci-common-0.15.0.tgz#4710e8a17c16b4951844ef82c4cbcccfb275c818" + integrity sha512-+ESEiQXhGKu5Es+ozf5feaVawlKqECzsjNwr9coxNZ31/rH8UQ5YA8xOUUtzJz5huZTfOg6kheySv7UlKQRyKw== + dependencies: + "@ovh-ux/manager-tailwind-config" "^0.2.1" + clsx "2.1.1" + date-fns "^3.6.0" + lodash.isequal "^4.5.0" + "@ovh-ux/manager-react-components@^1.41.1", "@ovh-ux/manager-react-components@^1.41.2": version "1.41.2" resolved "https://registry.yarnpkg.com/@ovh-ux/manager-react-components/-/manager-react-components-1.41.2.tgz#087cacbbff37c594201851f5f27d2a0c524545b5" From 13cbdcaa785dfd380d7fba1f64d98219d00e8128 Mon Sep 17 00:00:00 2001 From: Simon Chaumet Date: Fri, 20 Dec 2024 16:45:20 +0100 Subject: [PATCH 2/3] feat(pci-block-storage): add deploy zone ref: TAPC-2112 Signed-off-by: Simon Chaumet --- .../translations/add/Messages_fr_FR.json | 12 +- .../src/api/data/availableVolumes.ts | 36 --- .../pci-block-storage/src/api/data/catalog.ts | 68 +++--- .../src/api/data/instance.ts | 1 + .../pci-block-storage/src/api/data/regions.ts | 27 +-- .../pci-block-storage/src/api/data/volume.ts | 5 +- .../src/api/hooks/useCatalog.ts | 10 + .../api/hooks/useConsumptionVolumesAddon.ts | 47 ---- .../src/api/hooks/useHas3AZRegion.ts | 12 + .../src/api/hooks/useInstance.ts | 7 +- .../api/hooks/useProjectsAvailableVolumes.ts | 19 -- .../src/api/hooks/useRegions.ts | 94 -------- .../src/api/hooks/useVolume.spec.tsx | 25 ++- .../src/api/hooks/useVolume.tsx | 17 +- .../exten-banner-beta/ExtenBannerBeta.tsx | 23 +- .../list/Actions.component.spec.tsx | 2 + .../src/hooks/useDatagridColumn.spec.tsx | 1 + .../src/pages/attach/AttachStorage.page.tsx | 15 +- .../src/pages/edit/Edit.page.tsx | 19 +- .../src/pages/new/New.page.tsx | 8 +- .../new/components/AvailabilityZoneStep.tsx | 33 ++- .../new/components/CapacityStep.component.tsx | 13 +- .../new/components/DeploymentModeSelector.tsx | 29 +++ .../new/components/DeploymentModeTile.tsx | 98 ++++++++ .../components/DeploymentModeTileSummary.tsx | 22 ++ .../pages/new/components/HighSpeedV2Infos.tsx | 28 ++- .../new/components/LocationStep.component.tsx | 177 +++++++++++---- .../pages/new/components/PriceEstimate.tsx | 15 +- .../components/ValidationStep.component.tsx | 10 +- .../components/VolumeTypeStep.component.tsx | 210 +++++++++--------- .../src/pages/new/components/website-link.ts | 23 ++ .../src/pages/new/form.type.ts | 9 +- .../src/pages/new/hooks/useVolumeStepper.ts | 64 +++--- 33 files changed, 674 insertions(+), 505 deletions(-) delete mode 100644 packages/manager/apps/pci-block-storage/src/api/data/availableVolumes.ts delete mode 100644 packages/manager/apps/pci-block-storage/src/api/hooks/useConsumptionVolumesAddon.ts create mode 100644 packages/manager/apps/pci-block-storage/src/api/hooks/useHas3AZRegion.ts delete mode 100644 packages/manager/apps/pci-block-storage/src/api/hooks/useProjectsAvailableVolumes.ts delete mode 100644 packages/manager/apps/pci-block-storage/src/api/hooks/useRegions.ts create mode 100644 packages/manager/apps/pci-block-storage/src/pages/new/components/DeploymentModeSelector.tsx create mode 100644 packages/manager/apps/pci-block-storage/src/pages/new/components/DeploymentModeTile.tsx create mode 100644 packages/manager/apps/pci-block-storage/src/pages/new/components/DeploymentModeTileSummary.tsx create mode 100644 packages/manager/apps/pci-block-storage/src/pages/new/components/website-link.ts diff --git a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_fr_FR.json b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_fr_FR.json index 2bdfa71cbe03..9c6332353916 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_fr_FR.json +++ b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_fr_FR.json @@ -25,5 +25,15 @@ "pci_projects_project_storages_blocks_add_save_form": "Création du volume en cours", "pci_projects_project_storages_blocks_add_error_query": "Une erreur est survenue lors de la récupération des régions : {{ message }}", "pci_projects_project_storages_blocks_add_success_message": "Le volume {{volume}} a été ajouté", - "pci_projects_project_storages_blocks_add_error_post": "Une erreur est survenue lors de l'ajout du volume {{ volume }} : {{ message }}" + "pci_projects_project_storages_blocks_add_error_post": "Une erreur est survenue lors de l'ajout du volume {{ volume }} : {{ message }}", + "pci_projects_project_storages_blocks_add_deployment_mode_title": "Sélectionnez un mode de déploiement", + "pci_projects_project_storages_blocks_add_deployment_mode_description": "Sélectionnez une configuration optimale pour assurer la disponibilité, la résilience et la latence appropriées pour vos données selon vos cas d’usage. En savoir plus", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region": "Région 1-AZ", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region": "Déploiement résilient et économique sur 1 zone de disponibilité.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region-3-az": "Région 3-AZ", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region-3-az": "Déploiement haute résilience/haute disponibilité pour vos applications critiques sur 3 zones de disponibilité.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_localzone": "Local Zone", + "pci_projects_project_storages_blocks_add_deployment_mode_description_localzone": "Déploiement de vos applications au plus près de vos utilisatrices et utilisateurs pour une faible latence et la résidence des données.", + "pci_projects_project_storages_blocks_add_deployment_mode_price_from": "A partir de {{price}} HT/Go/heure", + "pci_projects_project_storages_blocks_add_beta_free": "Gratuit" } diff --git a/packages/manager/apps/pci-block-storage/src/api/data/availableVolumes.ts b/packages/manager/apps/pci-block-storage/src/api/data/availableVolumes.ts deleted file mode 100644 index bae1be8ff819..000000000000 --- a/packages/manager/apps/pci-block-storage/src/api/data/availableVolumes.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { v6 } from '@ovh-ux/manager-core-api'; -import { TLocalisation } from '@/api/hooks/useRegions'; - -export type TAvailableVolumesResponse = { - plans: { - code: string; - regions: { - name: string; - enabled: boolean; - type: string; - }[]; - }[]; -}; - -export const getProjectsAvailableVolumes = async ( - projectId: string, - ovhSubsidiary: string, -): Promise => { - const { data } = await v6.get( - `/cloud/project/${projectId}/capabilities/productAvailability?addonFamily=volume&ovhSubsidiary=${ovhSubsidiary}`, - ); - - return data; -}; - -export function isRegionWith3AZ(region: Pick) { - return region.type === 'region-3-az'; -} - -/** - * TODO: use real informations - * @param planCode - */ -export function isProductWithAvailabilityZone(planCode: string) { - return planCode.startsWith('volume.high-speed'); -} diff --git a/packages/manager/apps/pci-block-storage/src/api/data/catalog.ts b/packages/manager/apps/pci-block-storage/src/api/data/catalog.ts index 192f6048ebd2..3b986f6312ca 100644 --- a/packages/manager/apps/pci-block-storage/src/api/data/catalog.ts +++ b/packages/manager/apps/pci-block-storage/src/api/data/catalog.ts @@ -1,28 +1,42 @@ -export type TPricing = { - capacities: string[]; - mode: string; - phase: number; - commitment: number; - description: string; - price: { - currencyCode: string; - text: string; - value: number; - }; - tax: number; - interval: number; - intervalUnit: string; - quantity: { - max?: number; - min?: number; - }; - repeat: { - max?: number; - min?: number; - }; - strategy: string; - mustBeCompleted: boolean; - type: string; - promotions: unknown[]; - engagementConfiguration?: unknown; +import { TAddon } from '@ovh-ux/manager-pci-common'; +import { v6 } from '@ovh-ux/manager-core-api'; +import { TRegion } from '@/api/data/regions'; + +export type TCatalogGroup = { + name: string; + tags: string[]; }; + +export type TVolumePricing = Pick & { + regions: TRegion['name'][]; + showAvailabilityZones: boolean; + interval: 'day' | 'hour' | 'month' | 'none'; + specs: TAddon['blobs']['technical']; +}; + +export type TVolumeCatalogFilter = { + [key in 'deployment' | 'region']: TCatalogGroup[]; +}; + +export type TVolumeCatalogElementFilter = { + [Property in keyof TVolumeCatalogFilter]?: TVolumeCatalogFilter[Property][number]['name'][]; +}; + +export type TVolumeAddon = { + name: string; + tags: string[]; + filters: TVolumeCatalogElementFilter; + pricings: TVolumePricing[]; +}; + +export type TVolumeCatalog = { + filters: TVolumeCatalogFilter; + regions: TRegion[]; + models: TVolumeAddon[]; +}; + +export const getVolumeCatalog = async ( + projectId: string, +): Promise => + (await v6.get(`/cloud/project/${projectId}/catalog/volume`)) + .data; diff --git a/packages/manager/apps/pci-block-storage/src/api/data/instance.ts b/packages/manager/apps/pci-block-storage/src/api/data/instance.ts index 4537681f5d29..f95aa1014de0 100644 --- a/packages/manager/apps/pci-block-storage/src/api/data/instance.ts +++ b/packages/manager/apps/pci-block-storage/src/api/data/instance.ts @@ -11,6 +11,7 @@ export interface Instance { planCode: string; operationIds: string[]; currentMonthOutgoingTraffic: number; + availabilityZone?: string; } export interface MonthlyBilling { diff --git a/packages/manager/apps/pci-block-storage/src/api/data/regions.ts b/packages/manager/apps/pci-block-storage/src/api/data/regions.ts index cb9f3fa65fcb..e9ddfabc8117 100644 --- a/packages/manager/apps/pci-block-storage/src/api/data/regions.ts +++ b/packages/manager/apps/pci-block-storage/src/api/data/regions.ts @@ -1,23 +1,12 @@ -import { fetchIcebergV6 } from '@ovh-ux/manager-core-api'; +import { TVolumeCatalogElementFilter } from '@/api/data/catalog'; export type TRegion = { name: string; - type: string; - status: string; - continentCode: string; - services: { - name: string; - status: string; - endpoint: string; - }[]; - datacenterLocation: string; -}; - -export const getProjectRegions = async ( - projectId: string, -): Promise => { - const { data } = await fetchIcebergV6({ - route: `/cloud/project/${projectId}/region`, - }); - return data; + type: 'region-3-az' | 'region' | 'localzone'; + availabilityZones: string[]; + isInMaintenance: boolean; + isActivated: boolean; + country: string; + filters: TVolumeCatalogElementFilter; + datacenter: string; }; diff --git a/packages/manager/apps/pci-block-storage/src/api/data/volume.ts b/packages/manager/apps/pci-block-storage/src/api/data/volume.ts index 7e2e0d2beb38..f13fb515fb3e 100644 --- a/packages/manager/apps/pci-block-storage/src/api/data/volume.ts +++ b/packages/manager/apps/pci-block-storage/src/api/data/volume.ts @@ -17,6 +17,7 @@ export type TVolume = { planCode: string; type: string; regionName: string; + availabilityZone: 'any' | string; }; export type VolumeOptions = { @@ -185,7 +186,7 @@ export interface AddVolumeProps { regionName: string; volumeCapacity: number; volumeType: string; - availabilityZone?: string; + availabilityZone: string | null; } export const addVolume = async ({ @@ -194,6 +195,7 @@ export const addVolume = async ({ regionName, volumeCapacity, volumeType, + availabilityZone, }: AddVolumeProps): Promise => { const { data } = await v6.post( `/cloud/project/${projectId}/region/${regionName}/volume`, @@ -201,6 +203,7 @@ export const addVolume = async ({ name, size: volumeCapacity, type: volumeType, + availabilityZone, }, ); diff --git a/packages/manager/apps/pci-block-storage/src/api/hooks/useCatalog.ts b/packages/manager/apps/pci-block-storage/src/api/hooks/useCatalog.ts index f71c5d1cafad..7b6c2488d34f 100644 --- a/packages/manager/apps/pci-block-storage/src/api/hooks/useCatalog.ts +++ b/packages/manager/apps/pci-block-storage/src/api/hooks/useCatalog.ts @@ -1,12 +1,16 @@ import { useQuery } from '@tanstack/react-query'; import { useMe } from '@ovh-ux/manager-react-components'; import { getCatalog } from '@ovh-ux/manager-pci-common'; +import { getVolumeCatalog } from '@/api/data/catalog'; export const getCatalogQuery = (ovhSubsidiary: string) => ({ queryKey: ['catalog'], queryFn: () => getCatalog(ovhSubsidiary), }); +/** + * @deprecated use {@link useVolumeCatalog} instead + */ export const useCatalog = () => { const { me } = useMe(); return useQuery({ @@ -14,3 +18,9 @@ export const useCatalog = () => { enabled: !!me, }); }; + +export const useVolumeCatalog = (projectId: string) => + useQuery({ + queryKey: ['projects', projectId, 'catalog', 'volume'], + queryFn: () => getVolumeCatalog(projectId), + }); diff --git a/packages/manager/apps/pci-block-storage/src/api/hooks/useConsumptionVolumesAddon.ts b/packages/manager/apps/pci-block-storage/src/api/hooks/useConsumptionVolumesAddon.ts deleted file mode 100644 index 9e041a711423..000000000000 --- a/packages/manager/apps/pci-block-storage/src/api/hooks/useConsumptionVolumesAddon.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { useEffect, useState } from 'react'; -import { TCatalog } from '@ovh-ux/manager-pci-common'; -import { TRegion } from '@/api/data/regions'; -import { useCatalog } from '@/api/hooks/useCatalog'; -import { useProjectsAvailableVolumes } from '@/api/hooks/useProjectsAvailableVolumes'; - -export const useConsumptionVolumesAddon = ( - projectId: string, - region: TRegion, -) => { - const [state, setState] = useState(undefined); - - const { data: catalog, isPending: isCatalogPending } = useCatalog(); - const { - data: availableVolumes, - isPending: isVolumesPending, - } = useProjectsAvailableVolumes(projectId); - const isPending = isCatalogPending || isVolumesPending; - - useEffect(() => { - if (catalog && availableVolumes && region) { - // Get volumes addons ids - const volumeAddonsIds = catalog.plans - .find((plan) => plan.planCode === 'project') - .addonFamilies.find(({ name }) => name === 'volume').addons; - - // Get volumes addons details - const totalConsumptionVolumeAddons = catalog.addons.filter( - (addon) => - volumeAddonsIds.includes(addon.planCode) && - addon.planCode.includes('consumption'), - ); - - const volumeAddonsIdss = availableVolumes.plans?.filter(({ regions }) => - regions.some(({ name }) => name === region.name), - ); - - setState( - totalConsumptionVolumeAddons.filter(({ planCode }) => - volumeAddonsIdss?.some(({ code }) => code === planCode), - ), - ); - } - }, [catalog, region, availableVolumes]); - - return { volumeTypes: state, isPending }; -}; diff --git a/packages/manager/apps/pci-block-storage/src/api/hooks/useHas3AZRegion.ts b/packages/manager/apps/pci-block-storage/src/api/hooks/useHas3AZRegion.ts new file mode 100644 index 000000000000..e42e99187b9c --- /dev/null +++ b/packages/manager/apps/pci-block-storage/src/api/hooks/useHas3AZRegion.ts @@ -0,0 +1,12 @@ +import { useVolumeCatalog } from '@/api/hooks/useCatalog'; + +export const useHas3AZRegion = (projectId: string) => { + const { data: volumeCatalog, isPending } = useVolumeCatalog(projectId); + + return { + has3AZ: + volumeCatalog?.filters.deployment.some((d) => d.name === 'region-3-az') || + false, + isPending, + }; +}; diff --git a/packages/manager/apps/pci-block-storage/src/api/hooks/useInstance.ts b/packages/manager/apps/pci-block-storage/src/api/hooks/useInstance.ts index 0e2709f8c3f3..feadd2f4cf88 100644 --- a/packages/manager/apps/pci-block-storage/src/api/hooks/useInstance.ts +++ b/packages/manager/apps/pci-block-storage/src/api/hooks/useInstance.ts @@ -11,8 +11,7 @@ export const getInstanceQueryKey = (projectId: string, instanceId: string) => [ export const useInstance = (projectId: string, instanceId: string) => useQuery({ queryKey: getInstanceQueryKey(projectId, instanceId), - queryFn: (): Promise> => - getInstance(projectId, instanceId), + queryFn: (): Promise => getInstance(projectId, instanceId), enabled: !!instanceId, }); @@ -25,7 +24,7 @@ export const getInstancesQueryKey = (projectId: string, region: string) => [ export const useInstances = (projectId: string, region: string) => useQuery({ queryKey: getInstancesQueryKey(projectId, region), - queryFn: (): Promise[]> => - getInstancesByRegion(projectId, region) as Promise[]>, + queryFn: (): Promise => + getInstancesByRegion(projectId, region) as Promise, enabled: !!region, }); diff --git a/packages/manager/apps/pci-block-storage/src/api/hooks/useProjectsAvailableVolumes.ts b/packages/manager/apps/pci-block-storage/src/api/hooks/useProjectsAvailableVolumes.ts deleted file mode 100644 index ab08f644f4e3..000000000000 --- a/packages/manager/apps/pci-block-storage/src/api/hooks/useProjectsAvailableVolumes.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { useMe } from '@ovh-ux/manager-react-components'; -import { getProjectsAvailableVolumes } from '@/api/data/availableVolumes'; - -export const getProjectsAvailableVolumesQuery = ( - projectId: string, - ovhSubsidiary: string, -) => ({ - queryKey: ['project', projectId, 'availableVolumes'], - queryFn: () => getProjectsAvailableVolumes(projectId, ovhSubsidiary), -}); - -export const useProjectsAvailableVolumes = (projectId: string) => { - const { me } = useMe(); - return useQuery({ - ...getProjectsAvailableVolumesQuery(projectId, me?.ovhSubsidiary), - enabled: !!me, - }); -}; diff --git a/packages/manager/apps/pci-block-storage/src/api/hooks/useRegions.ts b/packages/manager/apps/pci-block-storage/src/api/hooks/useRegions.ts deleted file mode 100644 index 39cc2c56c89c..000000000000 --- a/packages/manager/apps/pci-block-storage/src/api/hooks/useRegions.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { useTranslation } from 'react-i18next'; -import { - getMacroRegion, - useTranslatedMicroRegions, -} from '@ovh-ux/manager-react-components'; -import { TRegion, getProjectRegions } from '@/api/data/regions'; - -export const useProjectRegions = (projectId: string) => - useQuery({ - queryKey: ['project', projectId, 'regions'], - queryFn: () => getProjectRegions(projectId), - }); - -export interface TContinent { - id: string; // unique ID - code: string; // continent code - name: string; // translated continent name -} - -export type TLocalisation = TRegion & { - isMacro: boolean; // is a macro region ? - isLocalZone: boolean; // is a localzone ? - macro: string; // associated macro region code - continentLabel: string; // WARNING: used to group regions - macroLabel: string; // translated macro region label - microLabel: string; // translated micro region label -}; - -export interface ProjectLocalisation { - regions: TLocalisation[]; - continents: TContinent[]; -} - -export const useProjectLocalisation = (projectId: string) => { - const { t: tLoc } = useTranslation('localisation'); - const { - translateMicroRegion, - translateMacroRegion, - translateContinentRegion, - } = useTranslatedMicroRegions(); - - return useQuery({ - queryKey: ['project', projectId, 'localisation'], - queryFn: () => getProjectRegions(projectId), - select: (data): ProjectLocalisation => { - const regions = data - .map((region) => { - const macro = getMacroRegion(region.name); - return { - ...region, - isMacro: region.name === macro, - macro, - macroLabel: translateMacroRegion(region.name) || macro, - microLabel: translateMicroRegion(region.name) || region.name, - continentLabel: translateContinentRegion(region.name) || macro, - isLocalZone: region.type === 'localzone', - }; - }) - .sort(({ name: a }, { name: b }) => { - const regionA = a.replace(/[\d]+/, ''); - const regionB = b.replace(/[\d]+/, ''); - if (regionA === regionB) { - const regionIdA = parseInt(a.replace(/[^\d]+/, ''), 10) || 0; - const regionIdB = parseInt(b.replace(/[^\d]+/, ''), 10) || 0; - return regionIdA - regionIdB; - } - return regionA.localeCompare(regionB); - }); - - const allContinents = { - id: 'WORLD', - code: 'WORLD', - name: tLoc('pci_project_regions_list_continent_all'), - }; - - const continents = [...new Set(regions.map((r) => r.continentLabel))].map( - (c) => { - const region = regions.find((r) => r.continentLabel === c); - return { - id: region.macro, - code: region.continentCode, - name: c, - }; - }, - ); - - return { - regions, - continents: [allContinents, ...continents], - }; - }, - }); -}; diff --git a/packages/manager/apps/pci-block-storage/src/api/hooks/useVolume.spec.tsx b/packages/manager/apps/pci-block-storage/src/api/hooks/useVolume.spec.tsx index 36eba921e20e..33d9c51279f8 100644 --- a/packages/manager/apps/pci-block-storage/src/api/hooks/useVolume.spec.tsx +++ b/packages/manager/apps/pci-block-storage/src/api/hooks/useVolume.spec.tsx @@ -29,12 +29,6 @@ vi.mock('@/api/data/volume', async (importOriginal) => { }; }); -vi.mock('@ovh-ux/manager-react-components', () => ({ - useTranslatedMicroRegions: vi.fn(() => ({ - translateMicroRegion: vi.fn((t) => t), - })), -})); - const queryClient = new QueryClient(); const wrapper = ({ children }) => ( @@ -64,6 +58,7 @@ describe('useVolume', () => { planCode: '', type: '', regionName: '', + availabilityZone: 'any', }; vi.mocked(getVolume).mockResolvedValue(volumeMock); @@ -98,11 +93,12 @@ describe('useVolumes', () => { size: 0, status: 'available', statusGroup: '', - region: 'region', + region: 'region1', bootable: false, planCode: '', type: '', regionName: '', + availabilityZone: 'any', }, { id: '2', @@ -113,11 +109,12 @@ describe('useVolumes', () => { size: 0, status: 'available', statusGroup: '', - region: 'region', + region: 'region2', bootable: false, planCode: '', type: '', regionName: '', + availabilityZone: 'any', }, ]; @@ -147,12 +144,13 @@ describe('useVolumes', () => { id: '1', name: 'Volume 1', planCode: '', - region: 'region', - regionName: 'region', + region: 'region1', + regionName: 'manager_components_region_region_micro', size: 0, status: 'available', statusGroup: 'ACTIVE', type: '', + availabilityZone: 'any', }, { attachedTo: [], @@ -162,12 +160,13 @@ describe('useVolumes', () => { id: '2', name: 'Volume 2', planCode: '', - region: 'region', - regionName: 'region', + region: 'region2', + regionName: 'manager_components_region_region_micro', size: 0, status: 'available', statusGroup: 'ACTIVE', type: '', + availabilityZone: 'any', }, ], totalRows: 2, @@ -250,6 +249,7 @@ describe('useAttachVolume', () => { planCode: '', type: '', regionName: '', + availabilityZone: 'any', }); const { result } = renderHook( @@ -291,6 +291,7 @@ describe('useDetachVolume', () => { planCode: '', type: '', regionName: '', + availabilityZone: 'any', }); const { result } = renderHook( diff --git a/packages/manager/apps/pci-block-storage/src/api/hooks/useVolume.tsx b/packages/manager/apps/pci-block-storage/src/api/hooks/useVolume.tsx index b50220bf896f..140acc7e2d01 100644 --- a/packages/manager/apps/pci-block-storage/src/api/hooks/useVolume.tsx +++ b/packages/manager/apps/pci-block-storage/src/api/hooks/useVolume.tsx @@ -1,7 +1,8 @@ import { useMemo } from 'react'; import { applyFilters, Filter } from '@ovh-ux/manager-core-api'; -import { useTranslatedMicroRegions } from '@ovh-ux/manager-react-components'; +import { getMacroRegion } from '@ovh-ux/manager-react-components'; import { useMutation, useQuery } from '@tanstack/react-query'; +import { useTranslation } from 'react-i18next'; import { addVolume, AddVolumeProps, @@ -22,7 +23,7 @@ import { UCENTS_FACTOR } from '@/hooks/currency-constants'; import queryClient from '@/queryClient'; export const useAllVolumes = (projectId: string) => { - const { translateMicroRegion } = useTranslatedMicroRegions(); + const { t } = useTranslation('region'); return useQuery({ queryKey: ['project', projectId, 'volumes'], queryFn: () => getAllVolumes(projectId), @@ -59,7 +60,15 @@ export const useAllVolumes = (projectId: string) => { return { ...volume, statusGroup, - regionName: translateMicroRegion(volume.region), + regionName: t( + `manager_components_region_${getMacroRegion(volume.region)}_micro`, + { + micro: + volume.availabilityZone && volume.availabilityZone !== 'any' + ? volume.availabilityZone + : volume.region, + }, + ), }; }), enabled: !!projectId, @@ -270,6 +279,7 @@ export const useAddVolume = ({ regionName, volumeCapacity, volumeType, + availabilityZone, onError, onSuccess, }: UseAddVolumeProps) => { @@ -281,6 +291,7 @@ export const useAddVolume = ({ regionName, volumeCapacity, volumeType, + availabilityZone, }), onError, onSuccess: async () => { diff --git a/packages/manager/apps/pci-block-storage/src/components/exten-banner-beta/ExtenBannerBeta.tsx b/packages/manager/apps/pci-block-storage/src/components/exten-banner-beta/ExtenBannerBeta.tsx index 8f18e6f7672c..d147c004ceed 100644 --- a/packages/manager/apps/pci-block-storage/src/components/exten-banner-beta/ExtenBannerBeta.tsx +++ b/packages/manager/apps/pci-block-storage/src/components/exten-banner-beta/ExtenBannerBeta.tsx @@ -9,34 +9,31 @@ import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; import { useMemo } from 'react'; import { FA_EXTEN_BANNER } from '@/api/data/quota'; -import { useProjectsAvailableVolumes } from '@/api/hooks/useProjectsAvailableVolumes'; +import { useVolumeCatalog } from '@/api/hooks/useCatalog'; -const extenProducts = [ - 'volume.high-speed-BETA.consumption', - 'volume.classic-BETA.consumption', -]; +const extenProducts = ['high-speed-BETA', 'classic-BETA']; function Banner() { const { t } = useTranslation('exten-banner-beta'); const { projectId } = useParams(); - const { data: availableVolumes } = useProjectsAvailableVolumes(projectId); + const { data: volumeCatalog } = useVolumeCatalog(projectId); const regionsString = useMemo( () => - availableVolumes + volumeCatalog ? [ ...new Set( - availableVolumes.plans - .filter((p) => extenProducts.includes(p.code)) - .flatMap((p) => p.regions) - .map((r) => r.name), + volumeCatalog.models + .filter((p) => extenProducts.includes(p.name)) + .flatMap((p) => p.pricings) + .flatMap((p) => p.regions), ), ].join(', ') : '', - [availableVolumes], + [volumeCatalog], ); - if (!availableVolumes) return null; + if (!volumeCatalog) return null; return ( diff --git a/packages/manager/apps/pci-block-storage/src/components/list/Actions.component.spec.tsx b/packages/manager/apps/pci-block-storage/src/components/list/Actions.component.spec.tsx index e7774577a2b7..cf7ed9ef9d77 100644 --- a/packages/manager/apps/pci-block-storage/src/components/list/Actions.component.spec.tsx +++ b/packages/manager/apps/pci-block-storage/src/components/list/Actions.component.spec.tsx @@ -25,6 +25,7 @@ const mockVolume: TVolume = { planCode: 'plan', type: 'ssd', regionName: 'US West 2', + availabilityZone: 'any', }; const mockVolumeDetach: TVolume = { id: '1', @@ -40,6 +41,7 @@ const mockVolumeDetach: TVolume = { planCode: 'plan', type: 'ssd', regionName: 'US West 2', + availabilityZone: 'any', }; describe('ActionsComponent', () => { it('ActionsComponent renders correct button with correct links', () => { diff --git a/packages/manager/apps/pci-block-storage/src/hooks/useDatagridColumn.spec.tsx b/packages/manager/apps/pci-block-storage/src/hooks/useDatagridColumn.spec.tsx index 209998a8e6c8..1c2d1ea7e86c 100644 --- a/packages/manager/apps/pci-block-storage/src/hooks/useDatagridColumn.spec.tsx +++ b/packages/manager/apps/pci-block-storage/src/hooks/useDatagridColumn.spec.tsx @@ -29,6 +29,7 @@ const volumeTestData: TVolume[] = [ planCode: 'plan-01', type: 'gp2', regionName: 'US West (Oregon)', + availabilityZone: 'any', }, ]; diff --git a/packages/manager/apps/pci-block-storage/src/pages/attach/AttachStorage.page.tsx b/packages/manager/apps/pci-block-storage/src/pages/attach/AttachStorage.page.tsx index 395cdebe0b90..c710e638aa6a 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/attach/AttachStorage.page.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/attach/AttachStorage.page.tsx @@ -7,7 +7,7 @@ import { } from '@ovhcloud/ods-components/react'; import { ODS_BUTTON_VARIANT, ODS_SPINNER_SIZE } from '@ovhcloud/ods-components'; import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { Translation, useTranslation } from 'react-i18next'; import { useNavigate, useParams } from 'react-router-dom'; import { useNotifications } from '@ovh-ux/manager-react-components'; @@ -22,10 +22,21 @@ export default function AttachStorage() { const { t } = useTranslation('attach'); const { addError, addSuccess } = useNotifications(); const { data: volume } = useVolume(projectId, volumeId); - const { data: instances, isPending: isInstancesPending } = useInstances( + const { data: dataInstances, isPending: isInstancesPending } = useInstances( projectId, volume?.region, ); + const instances = useMemo( + () => + !!dataInstances && + !!volume.availabilityZone && + volume.availabilityZone !== 'any' + ? dataInstances.filter( + (i) => i.availabilityZone === volume.availabilityZone, + ) + : dataInstances, + [dataInstances, volume], + ); const [selectedInstance, setSelectedInstance] = useState(null); const onClose = () => navigate('..'); diff --git a/packages/manager/apps/pci-block-storage/src/pages/edit/Edit.page.tsx b/packages/manager/apps/pci-block-storage/src/pages/edit/Edit.page.tsx index 617268f6c1c4..7de5724bc38a 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/edit/Edit.page.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/edit/Edit.page.tsx @@ -43,7 +43,7 @@ import HidePreloader from '@/core/HidePreloader'; import { useVolumeMaxSize } from '@/api/data/quota'; import { useRegionsQuota } from '@/api/hooks/useQuota'; import { PriceEstimate } from '@/pages/new/components/PriceEstimate'; -import { useCatalog } from '@/api/hooks/useCatalog'; +import { useVolumeCatalog } from '@/api/hooks/useCatalog'; type TFormState = { name: string; @@ -84,18 +84,23 @@ export default function EditPage() { isLoading: isLoadingVolume, isPending: isPendingVolume, } = useVolume(projectId, volumeId); - const { data: catalog } = useCatalog(); + const { data: catalog } = useVolumeCatalog(projectId); const catalogVolume = useMemo(() => { if (!!catalog && !!volume) { - return ( - catalog.addons.find((addon) => addon.planCode === volume.planCode) || - null - ); + return catalog.models.find((addon) => addon.name === volume.type) || null; } return null; }, [catalog, volume]); + const pricing = useMemo( + () => + catalogVolume + ? catalogVolume.pricings.find((p) => p.regions.includes(volume.region)) + : null, + [catalogVolume, volume], + ); + const { volumeMaxSize } = useVolumeMaxSize(volume?.region); const { @@ -427,7 +432,7 @@ export default function EditPage() {
)} diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/New.page.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/New.page.tsx index ddd1cea561b6..b0b774ea0924 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/New.page.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/new/New.page.tsx @@ -43,7 +43,8 @@ export default function NewPage(): JSX.Element { name: stepper.form.volumeName, regionName: stepper.form.region?.name, volumeCapacity: stepper.form.volumeCapacity, - volumeType: stepper.form.volumeType?.blobs.technical.name, + volumeType: stepper.form.volumeType?.name, + availabilityZone: stepper.form.availabilityZone, onSuccess: () => { navigate('..'); addSuccess( @@ -159,7 +160,7 @@ export default function NewPage(): JSX.Element { {!!stepper.form.region?.name && ( )} @@ -179,6 +180,7 @@ export default function NewPage(): JSX.Element { projectId={projectId} region={stepper.form.region} volumeType={stepper.form.volumeType} + pricing={stepper.form.pricing} step={stepper.capacity.step} onSubmit={stepper.capacity.submit} /> @@ -206,7 +208,7 @@ export default function NewPage(): JSX.Element { > { clearNotifications(); stepper.validation.submit(); diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/components/AvailabilityZoneStep.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/components/AvailabilityZoneStep.tsx index ad67d99e879f..6a8750b90b20 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/components/AvailabilityZoneStep.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/new/components/AvailabilityZoneStep.tsx @@ -1,5 +1,5 @@ import { TilesInputComponent } from '@ovh-ux/manager-react-components'; -import { useMemo, useState } from 'react'; +import { useState } from 'react'; import { OsdsButton, OsdsText } from '@ovhcloud/ods-components/react'; import { ODS_BUTTON_SIZE } from '@ovhcloud/ods-components'; import { @@ -9,36 +9,35 @@ import { } from '@ovhcloud/ods-common-theming'; import { useTranslation } from 'react-i18next'; import { Step } from '@/pages/new/hooks/useStep'; +import { TRegion } from '@/api/data/regions'; -type Props = { - regionName: string; +interface AvailabilityZoneStepProps { + region: TRegion; step: Step; onSubmit: (zone: string) => void; -}; +} -export function AvailabilityZoneStep({ regionName, step, onSubmit }: Props) { +export function AvailabilityZoneStep({ + region, + step, + onSubmit, +}: Readonly) { const { t } = useTranslation('stepper'); - // TODO: use real informations - const zones = useMemo( - () => - ['a', 'b', 'c'].map((suffix) => `${regionName.toLowerCase()}-${suffix}`), - [regionName], - ); const [selectedZone, setSelectedZone] = useState( undefined, ); - const displayedZones = useMemo( - () => (!!selectedZone && step.isLocked ? [selectedZone] : zones), - [zones, selectedZone, step], - ); return (
- items={displayedZones} + items={ + step.isLocked && selectedZone + ? [selectedZone] + : region.availabilityZones + } value={selectedZone} - onInput={(z) => setSelectedZone(z)} + onInput={setSelectedZone} label={(z) => (
diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/components/CapacityStep.component.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/components/CapacityStep.component.tsx index 0b4a2d059a92..210690846080 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/components/CapacityStep.component.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/new/components/CapacityStep.component.tsx @@ -22,21 +22,22 @@ import { } from '@ovhcloud/ods-components'; import { useTranslation } from 'react-i18next'; -import { TAddon } from '@ovh-ux/manager-pci-common'; import { PriceEstimate } from '@/pages/new/components/PriceEstimate'; import { HighSpeedV2Infos } from '@/pages/new/components/HighSpeedV2Infos'; -import { TLocalisation } from '@/api/hooks/useRegions'; import { StepState } from '@/pages/new/hooks/useStep'; import { useRegionsQuota } from '@/api/hooks/useQuota'; import { useVolumeMaxSize } from '@/api/data/quota'; +import { TVolumeAddon, TVolumePricing } from '@/api/data/catalog'; +import { TRegion } from '@/api/data/regions'; export const VOLUME_MIN_SIZE = 10; // 10 Gio export const VOLUME_UNLIMITED_QUOTA = -1; // Should be 10 * 1024 (but API is wrong) interface CapacityStepProps { projectId: string; - region: TLocalisation; - volumeType: TAddon; + region: TRegion; + volumeType: TVolumeAddon; + pricing: TVolumePricing; step: StepState; onSubmit: (volumeCapacity: number) => void; } @@ -45,6 +46,7 @@ export function CapacityStep({ projectId, region, volumeType, + pricing, step, onSubmit, }: Readonly) { @@ -146,8 +148,9 @@ export function CapacityStep({ - +
{volumeCapacity < VOLUME_MIN_SIZE && (
diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/components/DeploymentModeSelector.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/components/DeploymentModeSelector.tsx new file mode 100644 index 000000000000..ff46ffdb58be --- /dev/null +++ b/packages/manager/apps/pci-block-storage/src/pages/new/components/DeploymentModeSelector.tsx @@ -0,0 +1,29 @@ +import { TilesInputComponent } from '@ovh-ux/manager-react-components'; +import { TCatalogGroup } from '@/api/data/catalog'; +import { DeploymentModeTile } from '@/pages/new/components/DeploymentModeTile'; + +interface DeploymentModeSelectorProps { + deploymentGroups: TCatalogGroup[]; + selectedRegionGroup?: TCatalogGroup | null; + onChange?: (group: TCatalogGroup) => void; +} + +export const DeploymentModeSelector = ({ + deploymentGroups, + selectedRegionGroup, + onChange, +}: DeploymentModeSelectorProps) => ( +
+ + items={deploymentGroups} + value={selectedRegionGroup} + onInput={onChange} + label={(group) => ( + + )} + /> +
+); diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/components/DeploymentModeTile.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/components/DeploymentModeTile.tsx new file mode 100644 index 000000000000..2d2231a78731 --- /dev/null +++ b/packages/manager/apps/pci-block-storage/src/pages/new/components/DeploymentModeTile.tsx @@ -0,0 +1,98 @@ +import { useTranslation } from 'react-i18next'; +import { OsdsChip, OsdsText } from '@ovhcloud/ods-components/react'; +import { + ODS_THEME_COLOR_INTENT, + ODS_THEME_TYPOGRAPHY_LEVEL, + ODS_THEME_TYPOGRAPHY_SIZE, +} from '@ovhcloud/ods-common-theming'; +import { ODS_CHIP_SIZE } from '@ovhcloud/ods-components'; +import { + Region3AZChip, + RegionGlobalzoneChip, + RegionLocalzoneChip, +} from '@ovh-ux/manager-pci-common'; +import { clsx } from 'clsx'; +import { TCatalogGroup } from '@/api/data/catalog'; + +export const RegionChipByGroupName = ({ + group, +}: Readonly<{ group: TCatalogGroup }>) => { + switch (group.name) { + case 'localzone': + return ; + case 'region': + return ; + case 'region-3-az': + return ; + default: + return null; + } +}; + +interface DeploymentModeTileProps { + group: TCatalogGroup; + selected?: boolean; +} + +export function DeploymentModeTile({ + group, + selected, +}: Readonly) { + const { t } = useTranslation(['add', 'common']); + + return ( +
+
+ + {t( + `pci_projects_project_storages_blocks_add_deployment_mode_title_${group.name}`, + )} + +
+
+ +
+
+ + {t( + `pci_projects_project_storages_blocks_add_deployment_mode_description_${group.name}`, + )} + +
+
+ {group.tags.includes('is_new') && ( +
+ + {t('common:pci_projects_project_storages_blocks_new')} + + + {t('pci_projects_project_storages_blocks_add_beta_free')} + +
+ )} +
+
+ ); +} diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/components/DeploymentModeTileSummary.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/components/DeploymentModeTileSummary.tsx new file mode 100644 index 000000000000..aa8919c17a6b --- /dev/null +++ b/packages/manager/apps/pci-block-storage/src/pages/new/components/DeploymentModeTileSummary.tsx @@ -0,0 +1,22 @@ +import { OsdsTile } from '@ovhcloud/ods-components/react'; +import { DeploymentModeTile } from '@/pages/new/components/DeploymentModeTile'; +import { TCatalogGroup } from '@/api/data/catalog'; + +interface DeploymentModeTileSummaryProps { + group: TCatalogGroup; +} + +export function DeploymentModeTileSummary({ + group, +}: Readonly) { + return ( +
+ + + +
+ ); +} diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/components/HighSpeedV2Infos.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/components/HighSpeedV2Infos.tsx index 9fc2281229b0..f5fd1f0c41ba 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/components/HighSpeedV2Infos.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/new/components/HighSpeedV2Infos.tsx @@ -5,9 +5,9 @@ import { ODS_THEME_TYPOGRAPHY_SIZE, } from '@ovhcloud/ods-common-theming'; import { useTranslation } from 'react-i18next'; -import { TAddon } from '@ovh-ux/manager-pci-common'; +import { TVolumeAddon, TVolumePricing } from '@/api/data/catalog'; -export const HIGHSPEED_V2_PLANCODE = 'volume.high-speed-gen2.consumption'; +export const HIGHSPEED_V2_PLANCODE = 'high-speed-gen2'; export function getDisplayUnit(unit: string) { const perGB = '/GB'; @@ -17,8 +17,11 @@ export function getDisplayUnit(unit: string) { return unit; } -export function computeBandwidthToAllocate(capacity: number, addon: TAddon) { - const { bandwidth } = addon.blobs.technical; +export function computeBandwidthToAllocate( + capacity: number, + pricing: TVolumePricing, +) { + const { bandwidth } = pricing.specs; const allocatedBandwidth = capacity * bandwidth.level; const maxBandwidthInMb = bandwidth.max * 1000; @@ -27,8 +30,11 @@ export function computeBandwidthToAllocate(capacity: number, addon: TAddon) { : `${maxBandwidthInMb} ${getDisplayUnit(bandwidth.unit)}`; } -export function computeIopsToAllocate(capacity: number, addon: TAddon) { - const { volume } = addon.blobs.technical; +export function computeIopsToAllocate( + capacity: number, + pricing: TVolumePricing, +) { + const { volume } = pricing.specs; const allocatedIops = capacity * volume.iops.level; return allocatedIops <= volume.iops.max @@ -38,15 +44,17 @@ export function computeIopsToAllocate(capacity: number, addon: TAddon) { export interface HighSpeedV2InfosProps { volumeCapacity: number; - volumeType: TAddon; + volumeType: TVolumeAddon; + pricing: TVolumePricing; } export function HighSpeedV2Infos({ volumeCapacity, volumeType, + pricing, }: HighSpeedV2InfosProps) { const { t } = useTranslation('add'); - const isHighSpeedV2 = volumeType?.planCode === HIGHSPEED_V2_PLANCODE; + const isHighSpeedV2 = volumeType.name === HIGHSPEED_V2_PLANCODE; return ( isHighSpeedV2 && (
@@ -56,7 +64,7 @@ export function HighSpeedV2Infos({ color={ODS_THEME_COLOR_INTENT.text} > {t('pci_projects_project_storages_blocks_add_size_bandwidth', { - bandwidth: computeBandwidthToAllocate(volumeCapacity, volumeType), + bandwidth: computeBandwidthToAllocate(volumeCapacity, pricing), })}
@@ -66,7 +74,7 @@ export function HighSpeedV2Infos({ color={ODS_THEME_COLOR_INTENT.text} > {t('pci_projects_project_storages_blocks_add_size_iops', { - iops: computeIopsToAllocate(volumeCapacity, volumeType), + iops: computeIopsToAllocate(volumeCapacity, pricing), })}
diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/components/LocationStep.component.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/components/LocationStep.component.tsx index eeecacc71618..a54835f452d5 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/components/LocationStep.component.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/new/components/LocationStep.component.tsx @@ -1,76 +1,175 @@ -import { useState } from 'react'; -import { OsdsButton } from '@ovhcloud/ods-components/react'; -import { ODS_BUTTON_SIZE } from '@ovhcloud/ods-components'; +import { useCallback, useContext, useMemo, useState } from 'react'; +import { + OsdsButton, + OsdsLink, + OsdsSpinner, + OsdsText, +} from '@ovhcloud/ods-components/react'; +import { + ODS_BUTTON_SIZE, + ODS_SPINNER_SIZE, + ODS_TEXT_LEVEL, + ODS_TEXT_SIZE, +} from '@ovhcloud/ods-components'; import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import { isDiscoveryProject, - useProject, + PCICommonContext, RegionSelector, RegionSummary, + TLocalisation, usePCICommonContextFactory, - PCICommonContext, + useProject, } from '@ovh-ux/manager-pci-common'; -import { TLocalisation } from '@/api/hooks/useRegions'; +import { Subtitle } from '@ovh-ux/manager-react-components'; +import { ShellContext } from '@ovh-ux/manager-react-shell-client'; +import { OdsHTMLAnchorElementTarget } from '@ovhcloud/ods-common-core'; import { StepState } from '@/pages/new/hooks/useStep'; -import { useProjectsAvailableVolumes } from '@/api/hooks/useProjectsAvailableVolumes'; -import { isRegionWith3AZ } from '@/api/data/availableVolumes'; +import { DeploymentModeSelector } from '@/pages/new/components/DeploymentModeSelector'; +import { useVolumeCatalog } from '@/api/hooks/useCatalog'; +import { TCatalogGroup } from '@/api/data/catalog'; +import { DeploymentModeTileSummary } from '@/pages/new/components/DeploymentModeTileSummary'; +import { useHas3AZRegion } from '@/api/hooks/useHas3AZRegion'; +import { TRegion } from '@/api/data/regions'; +import { GLOBAL_INFRASTRUCTURE_URL } from '@/pages/new/components/website-link'; interface LocationProps { projectId: string; step: StepState; - onSubmit: (region: TLocalisation) => void; + onSubmit: (region: TRegion) => void; } -const useHas3AZRegion = (projectId: string) => { - const { data: availableVolumes, isPending } = useProjectsAvailableVolumes( - projectId, - ); - - return { - has3AZ: - availableVolumes?.plans.some((p) => p.regions.some(isRegionWith3AZ)) || - false, - isPending, - }; -}; - export function LocationStep({ projectId, step, - onSubmit, + onSubmit: parentSubmit, }: Readonly) { - const { t: tStepper } = useTranslation('stepper'); - const [region, setRegion] = useState(undefined); + const { t } = useTranslation(['stepper', 'add']); + const { data: volumeCatalog, isPending } = useVolumeCatalog(projectId); + const context = useContext(ShellContext); + const { ovhSubsidiary } = context.environment.getUser(); + + const [ + selectedRegionGroup, + setSelectedRegionGroup, + ] = useState(null); + const [selectedLocalisation, setSelectedLocalisation] = useState< + TLocalisation + >(undefined); + const selectedRegion = useMemo( + () => + selectedLocalisation + ? volumeCatalog.regions.find( + (r) => r.name === selectedLocalisation.name, + ) + : null, + [volumeCatalog, selectedLocalisation], + ); + const { data: project } = useProject(); const isDiscovery = isDiscoveryProject(project); - const hasRegion = !!region; + const hasRegion = !!selectedLocalisation; const { has3AZ } = useHas3AZRegion(projectId); const pciCommonProperties = usePCICommonContextFactory({ has3AZ }); + const regions = useMemo( + () => + selectedRegionGroup + ? volumeCatalog?.regions.filter((r) => + r.filters.deployment.includes(selectedRegionGroup.name), + ) + : volumeCatalog?.regions, + [volumeCatalog, selectedRegionGroup], + ); + + const onSubmit = useCallback(() => { + setSelectedRegionGroup( + volumeCatalog.filters.deployment.find( + (g) => g.name === selectedRegion.filters.deployment[0], + ), + ); + parentSubmit(selectedRegion); + }, [volumeCatalog, selectedRegion, parentSubmit]); + + if (isPending) return ; + return ( - {hasRegion && step.isLocked && } + {hasRegion && step.isLocked && ( +
+ + +
+ )} {(!step.isLocked || isDiscovery) && ( - - r.isMacro || - r.services.some((s) => s.name === 'volume' && s.status === 'UP') || - r.type === 'region-3-az' - } - /> +
+
+ + {t( + 'add:pci_projects_project_storages_blocks_add_deployment_mode_title', + )} + +
+ + + ), + }} + /> + +
+
+
+ { + setSelectedLocalisation(undefined); + setSelectedRegionGroup(group); + }} + /> +
+ +
+ + {t('add:pci_projects_project_storages_blocks_add_region_title')} + +
+
+ + r.isMacro || regions.some((r2) => r2.name === r.name) + } + /> +
+
)} {hasRegion && !step.isLocked && ( onSubmit(region)} + onClick={onSubmit} > - {tStepper('common_stepper_next_button_label')} + {t('common_stepper_next_button_label')} )}
diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/components/PriceEstimate.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/components/PriceEstimate.tsx index b0d576b66555..0f5fade2fc07 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/components/PriceEstimate.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/new/components/PriceEstimate.tsx @@ -9,24 +9,21 @@ import { useCatalogPrice, convertHourlyPriceToMonthly, } from '@ovh-ux/manager-react-components'; -import { TAddon } from '@ovh-ux/manager-pci-common'; +import { TVolumePricing } from '@/api/data/catalog'; export interface PriceEstimateProps { volumeCapacity: number; - volumeType: TAddon; + pricing: TVolumePricing; } -export function PriceEstimate({ - volumeCapacity, - volumeType, -}: PriceEstimateProps) { +export function PriceEstimate({ volumeCapacity, pricing }: PriceEstimateProps) { const { t } = useTranslation('add'); const { getFormattedCatalogPrice } = useCatalogPrice(3, { hideTaxLabel: true, }); - const { price } = - volumeType?.pricings?.find(({ type }) => type === 'consumption') || {}; - const priceEstimate = convertHourlyPriceToMonthly(price * volumeCapacity); + const priceEstimate = convertHourlyPriceToMonthly( + pricing.price * volumeCapacity, + ); return ( void; } export function ValidationStep({ volumeCapacity, - volumeType, + pricing, onSubmit, }: Readonly) { const { t } = useTranslation('add'); const navigate = useNavigate(); const { clearNotifications } = useNotifications(); + return (
- +
diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/components/VolumeTypeStep.component.tsx b/packages/manager/apps/pci-block-storage/src/pages/new/components/VolumeTypeStep.component.tsx index 0b9a11cb0b18..a6a002db4804 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/components/VolumeTypeStep.component.tsx +++ b/packages/manager/apps/pci-block-storage/src/pages/new/components/VolumeTypeStep.component.tsx @@ -1,37 +1,111 @@ -import { - OsdsButton, - OsdsSpinner, - OsdsText, - OsdsChip, -} from '@ovhcloud/ods-components/react'; +import { OsdsButton, OsdsText, OsdsChip } from '@ovhcloud/ods-components/react'; import { ODS_THEME_COLOR_INTENT, ODS_THEME_TYPOGRAPHY_LEVEL, ODS_THEME_TYPOGRAPHY_SIZE, } from '@ovhcloud/ods-common-theming'; -import { - ODS_CHIP_SIZE, - ODS_BUTTON_SIZE, - ODS_SPINNER_SIZE, -} from '@ovhcloud/ods-components'; +import { ODS_CHIP_SIZE, ODS_BUTTON_SIZE } from '@ovhcloud/ods-components'; import { TilesInputComponent, useCatalogPrice, } from '@ovh-ux/manager-react-components'; import { useTranslation } from 'react-i18next'; -import { useState } from 'react'; -import { TAddon, TCatalog } from '@ovh-ux/manager-pci-common'; +import { useMemo, useState } from 'react'; import { useTranslateBytes } from '@/pages/new/hooks/useTranslateBytes'; -import { useConsumptionVolumesAddon } from '@/api/hooks/useConsumptionVolumesAddon'; import { StepState } from '@/pages/new/hooks/useStep'; -import { TLocalisation } from '@/api/hooks/useRegions'; +import { useVolumeCatalog } from '@/api/hooks/useCatalog'; +import { TVolumeAddon } from '@/api/data/catalog'; +import { TRegion } from '@/api/data/regions'; + +const BETA_TAG = 'is_new'; + +function VolumeTypeTile({ + volumeType, + is3AZRegionSelected, +}: Readonly<{ volumeType: TVolumeAddon; is3AZRegionSelected: boolean }>) { + const { t } = useTranslation(['add', 'common']); + const tBytes = useTranslateBytes(); + const { getFormattedCatalogPrice } = useCatalogPrice(6, { + hideTaxLabel: true, + }); + + const pricing = volumeType.pricings[0]; + + const isNew = useMemo(() => volumeType.tags.includes(BETA_TAG), [volumeType]); + + return ( +
+
+ + {volumeType.name === 'classic' && is3AZRegionSelected + ? 'Classic 3AZ' + : volumeType.name} + + {isNew && ( + + {t('common:pci_projects_project_storages_blocks_new')} + + )} +
+
+ + {pricing.specs.volume.iops.guaranteed + ? t( + 'pci_projects_project_storages_blocks_add_type_addon_iops_guaranteed', + { + iops: pricing.specs.volume.iops.level, + separator: ', ', + }, + ) + : t( + 'pci_projects_project_storages_blocks_add_type_addon_iops_not_guaranteed', + { + iops: + pricing.specs.volume.iops.max || + pricing.specs.volume.iops.level, + separator: ', ', + }, + )} + {t( + 'pci_projects_project_storages_blocks_add_type_addon_capacity_max', + { + capacity: tBytes( + pricing.specs.volume.capacity.max, + 0, + false, + 'GB', + false, + ), + }, + )}{' '} +
+ {t('pci_projects_project_storages_blocks_add_type_addon_price', { + price: getFormattedCatalogPrice(volumeType.pricings[0]?.price), + })} +
+
+
+ ); +} export interface VolumeTypeStepProps { projectId: string; - region: TLocalisation; + region: TRegion; step: StepState; - onSubmit: (volumeType: TAddon) => void; + onSubmit: (volumeType: TVolumeAddon) => void; } export function VolumeTypeStep({ @@ -40,97 +114,35 @@ export function VolumeTypeStep({ step, onSubmit, }: Readonly) { - const { t } = useTranslation('add'); - const { t: tStepper } = useTranslation('stepper'); - const { t: tCommon } = useTranslation('common'); - const [volumeType, setVolumeType] = useState(undefined); - const tBytes = useTranslateBytes(); - const { getFormattedCatalogPrice } = useCatalogPrice(6, { - hideTaxLabel: true, - }); + const { t } = useTranslation('stepper'); + const { data } = useVolumeCatalog(projectId); - const { volumeTypes, isPending } = useConsumptionVolumesAddon( - projectId, - region, + const [volumeType, setVolumeType] = useState(undefined); + + const volumeTypes = useMemo( + () => + data?.models + .map((m) => ({ + ...m, + pricings: m.pricings.filter((p) => p.regions.includes(region.name)), + })) + .filter((m) => m.pricings.length > 0) || [], + [data, region], ); const displayedTypes = volumeType && step.isLocked ? [volumeType] : volumeTypes; - if (isPending) { - return ; - } - return ( <> - + value={volumeType} items={displayedTypes || []} - label={(vType: TCatalog['addons'][0]) => ( -
-
- - {vType.blobs.technical.name} - - {vType.blobs.tags.includes('is_new') && ( - - {tCommon('pci_projects_project_storages_blocks_new')} - - )} -
-
- - {vType.blobs.technical.volume.iops.guaranteed - ? t( - 'pci_projects_project_storages_blocks_add_type_addon_iops_guaranteed', - { - iops: vType.blobs.technical.volume.iops.level, - separator: ', ', - }, - ) - : t( - 'pci_projects_project_storages_blocks_add_type_addon_iops_not_guaranteed', - { - iops: - vType.blobs.technical.volume.iops.max || - vType.blobs.technical.volume.iops.level, - separator: ', ', - }, - )} - {t( - 'pci_projects_project_storages_blocks_add_type_addon_capacity_max', - { - capacity: tBytes( - vType.blobs.technical.volume.capacity.max, - 0, - false, - 'GB', - false, - ), - }, - )}{' '} -
- {t( - 'pci_projects_project_storages_blocks_add_type_addon_price', - { - price: getFormattedCatalogPrice(vType.pricings[0]?.price), - }, - )} -
-
-
+ label={(vType: TVolumeAddon) => ( + )} onInput={setVolumeType} /> @@ -142,7 +154,7 @@ export function VolumeTypeStep({ onClick={() => onSubmit(volumeType)} className="w-fit" > - {tStepper('common_stepper_next_button_label')} + {t('common_stepper_next_button_label')}
)} diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/components/website-link.ts b/packages/manager/apps/pci-block-storage/src/pages/new/components/website-link.ts new file mode 100644 index 000000000000..4a3b57c02689 --- /dev/null +++ b/packages/manager/apps/pci-block-storage/src/pages/new/components/website-link.ts @@ -0,0 +1,23 @@ +export const GLOBAL_INFRASTRUCTURE_URL = { + ASIA: 'https://www.ovhcloud.com/asia/about-us/global-infrastructure/', + AU: 'https://www.ovhcloud.com/en-au/about-us/global-infrastructure/', + CA: 'https://www.ovhcloud.com/en-ca/about-us/global-infrastructure/', + GB: 'https://www.ovhcloud.com/en-gb/about-us/global-infrastructure/', + IE: 'https://www.ovhcloud.com/en-ie/about-us/global-infrastructure/', + IN: 'https://www.ovhcloud.com/en-in/about-us/global-infrastructure/', + SG: 'https://www.ovhcloud.com/en-sg/about-us/global-infrastructure/', + DE: 'https://www.ovhcloud.com/de/about-us/global-infrastructure/', + ES: 'https://www.ovhcloud.com/es-es/about-us/global-infrastructure/', + FR: 'https://www.ovhcloud.com/fr/about-us/global-infrastructure/', + IT: 'https://www.ovhcloud.com/it/about-us/global-infrastructure/', + MA: 'https://www.ovhcloud.com/fr-ma/about-us/global-infrastructure/', + SN: 'https://www.ovhcloud.com/fr-sn/about-us/global-infrastructure/', + TN: 'https://www.ovhcloud.com/fr-tn/about-us/global-infrastructure/', + NL: 'https://www.ovhcloud.com/nl/about-us/global-infrastructure/', + PL: 'https://www.ovhcloud.com/pl/about-us/global-infrastructure/', + PT: 'https://www.ovhcloud.com/pt/about-us/global-infrastructure/', + QC: 'https://www.ovhcloud.com/fr-ca/about-us/global-infrastructure/', + US: 'https://www.ovhcloud.com/en/about-us/global-infrastructure/', + WS: 'https://www.ovhcloud.com/es/about-us/global-infrastructure/', + DEFAULT: 'https://www.ovhcloud.com/en/about-us/global-infrastructure/', +}; diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/form.type.ts b/packages/manager/apps/pci-block-storage/src/pages/new/form.type.ts index 2752ed1ee007..048413a06067 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/form.type.ts +++ b/packages/manager/apps/pci-block-storage/src/pages/new/form.type.ts @@ -1,9 +1,10 @@ -import { TAddon } from '@ovh-ux/manager-pci-common'; -import { TLocalisation } from '@/api/hooks/useRegions'; +import { TVolumeAddon, TVolumePricing } from '@/api/data/catalog'; +import { TRegion } from '@/api/data/regions'; export type TFormState = { - region: TLocalisation; - volumeType: TAddon; + region: TRegion; + volumeType: TVolumeAddon; + pricing: TVolumePricing; volumeName: string; volumeCapacity: number; availabilityZone: string; diff --git a/packages/manager/apps/pci-block-storage/src/pages/new/hooks/useVolumeStepper.ts b/packages/manager/apps/pci-block-storage/src/pages/new/hooks/useVolumeStepper.ts index 05c3b89f698a..8c2b11341770 100644 --- a/packages/manager/apps/pci-block-storage/src/pages/new/hooks/useVolumeStepper.ts +++ b/packages/manager/apps/pci-block-storage/src/pages/new/hooks/useVolumeStepper.ts @@ -1,18 +1,14 @@ -import { useEffect, useMemo, useState } from 'react'; -import { TAddon, useProjectRegions } from '@ovh-ux/manager-pci-common'; +import { useEffect, useState } from 'react'; import { Step, useStep } from '@/pages/new/hooks/useStep'; import { TFormState } from '@/pages/new/form.type'; -import { TLocalisation } from '@/api/hooks/useRegions'; -import { - isProductWithAvailabilityZone, - isRegionWith3AZ, -} from '@/api/data/availableVolumes'; +import { TVolumeAddon } from '@/api/data/catalog'; +import { useHas3AZRegion } from '@/api/hooks/useHas3AZRegion'; +import { TRegion } from '@/api/data/regions'; +import { useVolumeCatalog } from '@/api/hooks/useCatalog'; export function useVolumeStepper(projectId: string) { - const { data } = useProjectRegions(projectId); - const is3AZAvailable = useMemo(() => !!data && data.some(isRegionWith3AZ), [ - data, - ]); + const { data: volumeCatalog } = useVolumeCatalog(projectId); + const { has3AZ } = useHas3AZRegion(projectId); const [form, setForm] = useState>({}); const locationStep = useStep({ isOpen: true }); @@ -23,12 +19,12 @@ export function useVolumeStepper(projectId: string) { const validationStep = useStep(); useEffect(() => { - if (is3AZAvailable) { + if (has3AZ) { availabilityZoneStep.show(); } else { availabilityZoneStep.hide(); } - }, [is3AZAvailable]); + }, [has3AZ]); const order = [ locationStep, @@ -58,12 +54,19 @@ export function useVolumeStepper(projectId: string) { }); setForm({}); }, - submit: (region: TLocalisation) => { + submit: (region: TRegion) => { locationStep.check(); locationStep.lock(); volumeTypeStep.open(); - if (is3AZAvailable) { - if (isRegionWith3AZ(region)) { + if (has3AZ) { + if ( + volumeCatalog.models.some((m) => + m.pricings.some( + (p) => + p.regions.includes(region.name) && p.showAvailabilityZones, + ), + ) + ) { availabilityZoneStep.show(); } else { availabilityZoneStep.hide(); @@ -91,14 +94,13 @@ export function useVolumeStepper(projectId: string) { }); setForm((f) => ({ region: f.region })); }, - submit: (volumeType: TAddon) => { + submit: (volumeType: TVolumeAddon) => { volumeTypeStep.check(); volumeTypeStep.lock(); - if ( - is3AZAvailable && - isRegionWith3AZ(form.region) && - isProductWithAvailabilityZone(volumeType.planCode) - ) { + const pricing = volumeType.pricings.find((p) => + p.regions.includes(form.region.name), + ); + if (has3AZ && pricing.showAvailabilityZones) { availabilityZoneStep.show(); availabilityZoneStep.open(); } else { @@ -108,24 +110,24 @@ export function useVolumeStepper(projectId: string) { setForm((f) => ({ ...f, volumeType, + pricing, })); }, }, availabilityZone: { step: availabilityZoneStep, edit: () => { - volumeTypeStep.unlock(); - [ - availabilityZoneStep, - capacityStep, - volumeNameStep, - validationStep, - ].forEach((step) => { + availabilityZoneStep.unlock(); + [capacityStep, volumeNameStep, validationStep].forEach((step) => { step.uncheck(); step.unlock(); step.close(); }); - setForm((f) => ({ region: f.region, volumeType: f.volumeType })); + setForm((f) => ({ + region: f.region, + volumeType: f.volumeType, + pricing: f.pricing, + })); }, submit: (availabilityZone: string) => { availabilityZoneStep.check(); @@ -149,6 +151,7 @@ export function useVolumeStepper(projectId: string) { setForm((f) => ({ region: f.region, volumeType: f.volumeType, + pricing: f.pricing, availabilityZone: f.availabilityZone, })); }, @@ -172,6 +175,7 @@ export function useVolumeStepper(projectId: string) { setForm((f) => ({ region: f.region, volumeType: f.volumeType, + pricing: f.pricing, availabilityZone: f.availabilityZone, volumeCapacity: f.volumeCapacity, })); From 7102b1da6425373bc889015e66ad188411913a45 Mon Sep 17 00:00:00 2001 From: CDS Translator Agent Date: Mon, 20 Jan 2025 08:38:44 +0000 Subject: [PATCH 3/3] fix(i18n): add missing translations [CDS 186] Signed-off-by: CDS Translator Agent --- .../public/translations/add/Messages_de_DE.json | 13 ++++++++++++- .../public/translations/add/Messages_en_GB.json | 13 ++++++++++++- .../public/translations/add/Messages_es_ES.json | 13 ++++++++++++- .../public/translations/add/Messages_fr_CA.json | 13 ++++++++++++- .../public/translations/add/Messages_it_IT.json | 13 ++++++++++++- .../public/translations/add/Messages_pl_PL.json | 13 ++++++++++++- .../public/translations/add/Messages_pt_PT.json | 13 ++++++++++++- 7 files changed, 84 insertions(+), 7 deletions(-) diff --git a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_de_DE.json b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_de_DE.json index 8011790e3c94..d982ae6d37ec 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_de_DE.json +++ b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_de_DE.json @@ -24,5 +24,16 @@ "pci_projects_project_storages_blocks_add_type_addon_capacity_max": "maximal {{capacity}}", "pci_projects_project_storages_blocks_add_type_addon_price": "{{price}} inkl. MwSt. / GB / Stunde", "pci_projects_project_storages_blocks_add_size_bandwidth": "Bandbreite: {{bandwidth}}", - "pci_projects_project_storages_blocks_add_size_iops": "IOPS: {{iops}}" + "pci_projects_project_storages_blocks_add_size_iops": "IOPS: {{iops}}", + "pci_projects_project_storages_blocks_add_availability_zone": "Verfügbarkeitszone auswählen", + "pci_projects_project_storages_blocks_add_deployment_mode_title": "Bereitstellungsmodus auswählen", + "pci_projects_project_storages_blocks_add_deployment_mode_description": "Wählen Sie eine optimale Konfiguration aus, um die Verfügbarkeit, Resilienz und Latenz zu gewährleisten, die für Ihre Daten am besten geeignet sind. Mehr erfahren", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region": "1-AZ Region", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region": "Resiliente und kostengünstige Bereitstellung in 1 Verfügbarkeitszone.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region-3-az": "3-AZ Region", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region-3-az": "Bereitstellung mit hoher Resilienz/Hochverfügbarkeit für kritische Anwendungen in 3 Verfügbarkeitszonen.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_localzone": "Local Zone", + "pci_projects_project_storages_blocks_add_deployment_mode_description_localzone": "Bereitstellung Ihrer Anwendungen nah an Ihren Benutzern bei geringer Latenz und geringem Datenspeicherbedarf.", + "pci_projects_project_storages_blocks_add_deployment_mode_price_from": "Ab {{price}} zzgl. MwSt./GB/Stunde", + "pci_projects_project_storages_blocks_add_beta_free": "Kostenlos" } diff --git a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_en_GB.json b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_en_GB.json index 0178449247a5..1bb3622891a3 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_en_GB.json +++ b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_en_GB.json @@ -24,5 +24,16 @@ "pci_projects_project_storages_blocks_add_type_addon_iops_guaranteed": "{{iops}} guaranteed IOPS{{separator}} ", "pci_projects_project_storages_blocks_add_type_addon_iops_not_guaranteed": "Up to {{iops}} IOPS{{separator}} ", "pci_projects_project_storages_blocks_add_type_addon_capacity_max": "{{capacity}} max", - "pci_projects_project_storages_blocks_add_type_addon_price": "{{price}} ex. VAT/GB/hour" + "pci_projects_project_storages_blocks_add_type_addon_price": "{{price}} ex. VAT/GB/hour", + "pci_projects_project_storages_blocks_add_availability_zone": "Select an availability zone", + "pci_projects_project_storages_blocks_add_deployment_mode_title": "Select a deployment mode", + "pci_projects_project_storages_blocks_add_deployment_mode_description": "Select an optimal configuration to ensure the availability, resilience and latency appropriate for your data depending on your use cases. Find out more", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region": "1-AZ Region", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region": "Resilient and cost-effective deployment on 1 availability zone.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region-3-az": " 3-AZ Region", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region-3-az": "High-resilience/high-availability deployment for your critical applications across 3 availability zones.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_localzone": "Local Zone", + "pci_projects_project_storages_blocks_add_deployment_mode_description_localzone": "Deploy your applications as close as possible to your users, for low latency and data residency.", + "pci_projects_project_storages_blocks_add_deployment_mode_price_from": "From {{price}} ex. VAT/GB/hour", + "pci_projects_project_storages_blocks_add_beta_free": "Free" } diff --git a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_es_ES.json b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_es_ES.json index 14dc83fba99f..fb026a95f995 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_es_ES.json +++ b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_es_ES.json @@ -24,5 +24,16 @@ "pci_projects_project_storages_blocks_add_type_addon_iops_guaranteed": "{{iops}} IOPS garantizadas{{separator}} ", "pci_projects_project_storages_blocks_add_type_addon_iops_not_guaranteed": "Hasta {{iops}} IOPS{{separator}} ", "pci_projects_project_storages_blocks_add_type_addon_capacity_max": "{{capacity}} máx.", - "pci_projects_project_storages_blocks_add_type_addon_price": "{{price}}/hora + IVA por GB" + "pci_projects_project_storages_blocks_add_type_addon_price": "{{price}}/hora + IVA por GB", + "pci_projects_project_storages_blocks_add_availability_zone": "Seleccione una zona de disponibilidad", + "pci_projects_project_storages_blocks_add_deployment_mode_title": "Seleccione un modo de despliegue", + "pci_projects_project_storages_blocks_add_deployment_mode_description": "Seleccione una configuración óptima para garantizar la disponibilidad, la resiliencia y la latencia adecuadas para sus datos en función de sus necesidades. Más información", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region": "Región 1-AZ", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region": "Implementación resiliente y económica en 1 zona de disponibilidad.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region-3-az": "Región 3-AZ", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region-3-az": "Despliegue de alta resiliencia/alta disponibilidad para sus aplicaciones críticas en 3 zonas de disponibilidad.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_localzone": "Local Zone", + "pci_projects_project_storages_blocks_add_deployment_mode_description_localzone": "Despliegue sus aplicaciones cerca de sus usuarios para una baja latencia y la residencia de los datos.", + "pci_projects_project_storages_blocks_add_deployment_mode_price_from": "A partir de {{price}}/hora + IVA por GB", + "pci_projects_project_storages_blocks_add_beta_free": "Gratis" } diff --git a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_fr_CA.json b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_fr_CA.json index 1081791c6f9e..9c6332353916 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_fr_CA.json +++ b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_fr_CA.json @@ -11,6 +11,7 @@ "pci_projects_project_storages_blocks_add_type_addon_iops_not_guaranteed": "Jusqu'à {{iops}} IOPS{{separator}} ", "pci_projects_project_storages_blocks_add_type_addon_capacity_max": "{{capacity}} max.", "pci_projects_project_storages_blocks_add_type_addon_price": "{{price}} HT/Go/heure", + "pci_projects_project_storages_blocks_add_availability_zone": "Sélectionnez une zone de disponibilité", "pci_projects_project_storages_blocks_add_size_title": "Capacité du volume", "pci_projects_project_storages_blocks_add_size_help": "La taille maximale dépend de votre quota disponible.", "pci_projects_project_storages_blocks_add_size_unit": "Go", @@ -24,5 +25,15 @@ "pci_projects_project_storages_blocks_add_save_form": "Création du volume en cours", "pci_projects_project_storages_blocks_add_error_query": "Une erreur est survenue lors de la récupération des régions : {{ message }}", "pci_projects_project_storages_blocks_add_success_message": "Le volume {{volume}} a été ajouté", - "pci_projects_project_storages_blocks_add_error_post": "Une erreur est survenue lors de l'ajout du volume {{ volume }} : {{ message }}" + "pci_projects_project_storages_blocks_add_error_post": "Une erreur est survenue lors de l'ajout du volume {{ volume }} : {{ message }}", + "pci_projects_project_storages_blocks_add_deployment_mode_title": "Sélectionnez un mode de déploiement", + "pci_projects_project_storages_blocks_add_deployment_mode_description": "Sélectionnez une configuration optimale pour assurer la disponibilité, la résilience et la latence appropriées pour vos données selon vos cas d’usage. En savoir plus", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region": "Région 1-AZ", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region": "Déploiement résilient et économique sur 1 zone de disponibilité.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region-3-az": "Région 3-AZ", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region-3-az": "Déploiement haute résilience/haute disponibilité pour vos applications critiques sur 3 zones de disponibilité.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_localzone": "Local Zone", + "pci_projects_project_storages_blocks_add_deployment_mode_description_localzone": "Déploiement de vos applications au plus près de vos utilisatrices et utilisateurs pour une faible latence et la résidence des données.", + "pci_projects_project_storages_blocks_add_deployment_mode_price_from": "A partir de {{price}} HT/Go/heure", + "pci_projects_project_storages_blocks_add_beta_free": "Gratuit" } diff --git a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_it_IT.json b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_it_IT.json index a7c42fa1c7ea..1810d670733b 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_it_IT.json +++ b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_it_IT.json @@ -24,5 +24,16 @@ "pci_projects_project_storages_blocks_add_type_addon_iops_guaranteed": "{{iops}} IOPS garantiti {{separator}} ", "pci_projects_project_storages_blocks_add_type_addon_iops_not_guaranteed": "Fino a {{iops}} IOPS {{separator}} ", "pci_projects_project_storages_blocks_add_type_addon_capacity_max": "{{capacity}} max.", - "pci_projects_project_storages_blocks_add_type_addon_price": "{{price}} +IVA/GB/ora" + "pci_projects_project_storages_blocks_add_type_addon_price": "{{price}} +IVA/GB/ora", + "pci_projects_project_storages_blocks_add_availability_zone": "Seleziona un'area di disponibilità", + "pci_projects_project_storages_blocks_add_deployment_mode_title": "Seleziona una modalità di distribuzione", + "pci_projects_project_storages_blocks_add_deployment_mode_description": "Seleziona la configurazione ottimale per garantire la disponibilità, la resilienza e la latenza appropriate per i dati in base alle modalità di utilizzo. Scopri di più", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region": "Region 1-AZ", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region": "Installazione resiliente ed economica su 1 zona di disponibilità.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region-3-az": "Region 3-AZ", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region-3-az": "Installazione ad alta resilienza/high availability per le applicazioni critiche su 3 zone di disponibilità.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_localzone": "Local Zone", + "pci_projects_project_storages_blocks_add_deployment_mode_description_localzone": "Implementazione delle applicazioni il più vicino possibile agli utenti per una latenza ridotta e la residenza dei dati.", + "pci_projects_project_storages_blocks_add_deployment_mode_price_from": "A partire da {{price}} +IVA/GB/ora", + "pci_projects_project_storages_blocks_add_beta_free": "Gratuito" } diff --git a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_pl_PL.json b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_pl_PL.json index 89eadffc0b75..5a3e40801b05 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_pl_PL.json +++ b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_pl_PL.json @@ -24,5 +24,16 @@ "pci_projects_project_storages_blocks_add_type_addon_iops_guaranteed": "Gwarantowana wartość IOPS: {{iops}}{{separator}} ", "pci_projects_project_storages_blocks_add_type_addon_iops_not_guaranteed": "Do {{iops}} IOPS{{separator}} ", "pci_projects_project_storages_blocks_add_type_addon_capacity_max": "Maksymalnie {{capacity}}", - "pci_projects_project_storages_blocks_add_type_addon_price": "{{price}} netto/GB/godz." + "pci_projects_project_storages_blocks_add_type_addon_price": "{{price}} netto/GB/godz.", + "pci_projects_project_storages_blocks_add_availability_zone": "Wybierz obszar", + "pci_projects_project_storages_blocks_add_deployment_mode_title": "Wybierz tryb wdrożenia", + "pci_projects_project_storages_blocks_add_deployment_mode_description": "Wybierz optymalną konfigurację, aby zapewnić dostępność, odporność i opóźnienie danych dopasowane do Twoich zastosowań. Dowiedz się więcej", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region": "Region 1-AZ", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region": "Odporne i ekonomiczne wdrożenie w 1 strefie dostępności.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region-3-az": "Region 3-AZ", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region-3-az": "Wdrożenie o wysokiej odporności/dostępności dla krytycznych aplikacji w 3 strefach dostępności.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_localzone": "Local Zone", + "pci_projects_project_storages_blocks_add_deployment_mode_description_localzone": "Wdrażanie aplikacji jak najbliżej użytkowników w celu zminimalizowania czasu odpowiedzi i zapewnienia rezydencji danych.", + "pci_projects_project_storages_blocks_add_deployment_mode_price_from": "Od {{price}} netto/GB/godz", + "pci_projects_project_storages_blocks_add_beta_free": "Gratis" } diff --git a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_pt_PT.json b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_pt_PT.json index f106415e2fc1..0f17e71fa6af 100644 --- a/packages/manager/apps/pci-block-storage/public/translations/add/Messages_pt_PT.json +++ b/packages/manager/apps/pci-block-storage/public/translations/add/Messages_pt_PT.json @@ -24,5 +24,16 @@ "pci_projects_project_storages_blocks_add_type_addon_iops_not_guaranteed": "Até {{iops}} IOPS {{separator}} ", "pci_projects_project_storages_blocks_add_type_addon_capacity_max": "{{capacity}} máx.", "pci_projects_project_storages_blocks_add_type_addon_price": "{{price}} /hora + IVA por GB", - "pci_projects_project_storages_blocks_add_size_iops": "IOPS: {{iops}}" + "pci_projects_project_storages_blocks_add_size_iops": "IOPS: {{iops}}", + "pci_projects_project_storages_blocks_add_availability_zone": "Selecione uma zona de disponibilidade", + "pci_projects_project_storages_blocks_add_deployment_mode_title": "Selecione um modo de implementação", + "pci_projects_project_storages_blocks_add_deployment_mode_description": "Escolha uma configuração ótima para assegurar a disponibilidade, a resiliência e a latência adequadas aos seus dados, consoante o seu caso de utilização. Saber mais", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region": "Região 1-AZ", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region": "Implementação resiliente e económica numa zona de disponibilidade.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_region-3-az": "Região 3-AZ", + "pci_projects_project_storages_blocks_add_deployment_mode_description_region-3-az": "Implementação de alta resiliência/alta disponibilidade para as suas aplicações críticas em 3 zonas de disponibilidade.", + "pci_projects_project_storages_blocks_add_deployment_mode_title_localzone": "Local Zone", + "pci_projects_project_storages_blocks_add_deployment_mode_description_localzone": "Implementação das suas aplicações mais próxima dos seus utilizadores para uma baixa latência e residência dos dados.", + "pci_projects_project_storages_blocks_add_deployment_mode_price_from": "A partir de {{price}} /GB/hora + IVA", + "pci_projects_project_storages_blocks_add_beta_free": "Grátis" }