diff --git a/packages/manager-components/src/components/navigation/menus/action/action.component.tsx b/packages/manager-components/src/components/navigation/menus/action/action.component.tsx index eca7a0b96ec7..f7498bd96914 100644 --- a/packages/manager-components/src/components/navigation/menus/action/action.component.tsx +++ b/packages/manager-components/src/components/navigation/menus/action/action.component.tsx @@ -26,17 +26,58 @@ export interface ActionMenuItem { download?: string; href?: string; target?: OdsHTMLAnchorElementTarget; + color?: ODS_THEME_COLOR_INTENT; onClick?: () => void; label: ReactI18NextChild | Iterable; } +export interface ActionMenuLayout { + isCompact?: boolean; + hasVerticalEllipsis?: boolean; +} + export interface ActionMenuProps { items: ActionMenuItem[]; + /** + * @deprecated Use layout.isCompact instead + * */ isCompact?: boolean; + layout?: ActionMenuLayout; } -export const ActionMenu: React.FC = ({ items, isCompact }) => { +const computeOdsIconProps = ( + layout: ActionMenuLayout, +): { + size: ODS_ICON_SIZE; + name: ODS_ICON_NAME; + color: ODS_THEME_COLOR_INTENT; +} => { + const size = layout.isCompact ? ODS_ICON_SIZE.xs : ODS_ICON_SIZE.xxs; + const color = ODS_THEME_COLOR_INTENT.primary; + + const compactIcon = layout.hasVerticalEllipsis + ? ODS_ICON_NAME.ELLIPSIS_VERTICAL + : ODS_ICON_NAME.ELLIPSIS; + + const name = layout.isCompact + ? compactIcon + : ODS_ICON_NAME.ARROW_DOWN_CONCEPT; + + return { size, name, color }; +}; + +export const ActionMenu: React.FC = ({ + items, + isCompact, + layout, +}) => { const { t } = useTranslation('buttons'); + const menuLayout: ActionMenuLayout = { + ...layout, + isCompact: isCompact || layout?.isCompact, + }; + const odsIconProps = computeOdsIconProps(menuLayout); + return ( = ({ items, isCompact }) => { type={ODS_BUTTON_TYPE.button} size={ODS_BUTTON_SIZE.sm} inline - circle={isCompact || undefined} + circle={menuLayout.isCompact || undefined} > - {!isCompact && t('common_actions')} - - + {!menuLayout.isCompact && t('common_actions')} + + @@ -67,7 +99,7 @@ export const ActionMenu: React.FC = ({ items, isCompact }) => { void; + onError?: (result: ApiError) => void; +}) => { + const [isErrorVisible, setIsErrorVisible] = React.useState(false); + const queryClient = useQueryClient(); + + const { + mutate: terminateKms, + isPending, + error: terminateKmsError, + } = useMutation({ + mutationKey: terminateOKmsQueryKey(kmsId), + mutationFn: async () => { + const { data: servicesId } = await queryClient.fetchQuery< + ApiResponse + >({ + queryKey: getOkmsServiceIdQueryKey({ okms: kmsId }), + queryFn: () => getOkmsServiceId({ okms: kmsId }), + }); + return terminateOKms({ serviceId: servicesId[0] }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: getOkmsServicesResourceListQueryKey, + }); + onSuccess?.(); + }, + onError: (result: ApiError) => { + setIsErrorVisible(true); + onError?.(result); + }, + }); + + return { + terminateKms, + isPending, + isErrorVisible: terminateKmsError && isErrorVisible, + error: terminateKmsError, + hideError: () => setIsErrorVisible(false), + }; +}; diff --git a/packages/manager/apps/key-management-service/src/api/services/post.ts b/packages/manager/apps/key-management-service/src/api/services/post.ts new file mode 100644 index 000000000000..2a32a8a65cc1 --- /dev/null +++ b/packages/manager/apps/key-management-service/src/api/services/post.ts @@ -0,0 +1,13 @@ +import { apiClient } from '@ovh-ux/manager-core-api'; + +export type TerminateKmsParams = { + serviceId: number; +}; + +export const terminateOKmsQueryKey = (kms: string) => [`terminateKms-${kms}`]; + +/** + * Terminiate a kms + */ +export const terminateOKms = async ({ serviceId }: TerminateKmsParams) => + apiClient.v6.post<{ message: string }>(`/services/${serviceId}/terminate`); diff --git a/packages/manager/apps/key-management-service/src/components/Breadcrumb/Breadcrumb.tsx b/packages/manager/apps/key-management-service/src/components/Breadcrumb/Breadcrumb.tsx index 6eeb9219938d..3dbf5bc81df4 100644 --- a/packages/manager/apps/key-management-service/src/components/Breadcrumb/Breadcrumb.tsx +++ b/packages/manager/apps/key-management-service/src/components/Breadcrumb/Breadcrumb.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { - useBreadcrumb, BreadcrumbProps, + useBreadcrumb, } from '@ovh-ux/manager-react-shell-client'; import { OsdsBreadcrumb } from '@ovhcloud/ods-components/react'; diff --git a/packages/manager/apps/key-management-service/src/components/Listing/ListingCells.tsx b/packages/manager/apps/key-management-service/src/components/Listing/ListingCells.tsx index b6865facfc30..2d0099f5ff60 100644 --- a/packages/manager/apps/key-management-service/src/components/Listing/ListingCells.tsx +++ b/packages/manager/apps/key-management-service/src/components/Listing/ListingCells.tsx @@ -8,6 +8,7 @@ import { ODS_TEXT_COLOR_INTENT } from '@ovhcloud/ods-components'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { OKMS } from '@/interface'; +import KmsActionMenu from '../menu/KmsActionMenu.component'; export const DatagridCellName = (props: OKMS) => { const navigate = useNavigate(); @@ -41,3 +42,7 @@ export const DatagridCellRegion = (props: OKMS) => { export const DatagridCellRestApiEndpoint = (props: OKMS) => { return {props?.restEndpoint}; }; + +export const DatagridActionMenu = (props: OKMS) => { + return ; +}; diff --git a/packages/manager/apps/key-management-service/src/components/Modal/terminate/TerminateModal.component.tsx b/packages/manager/apps/key-management-service/src/components/Modal/terminate/TerminateModal.component.tsx new file mode 100644 index 000000000000..a76a57663b92 --- /dev/null +++ b/packages/manager/apps/key-management-service/src/components/Modal/terminate/TerminateModal.component.tsx @@ -0,0 +1,113 @@ +import { ApiError } from '@ovh-ux/manager-core-api'; +import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; +import { + ODS_BUTTON_TYPE, + ODS_BUTTON_VARIANT, + ODS_INPUT_TYPE, + ODS_MESSAGE_TYPE, + ODS_SPINNER_SIZE, + ODS_TEXT_LEVEL, + ODS_TEXT_SIZE, + OdsInputValueChangeEvent, +} from '@ovhcloud/ods-components'; +import { + OsdsButton, + OsdsInput, + OsdsMessage, + OsdsModal, + OsdsSpinner, + OsdsText, +} from '@ovhcloud/ods-components/react'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { terminateValue } from './TerminateModal.constants'; + +export type TerminateModalProps = { + headline: string; + description?: string; + terminateInputButton: string; + closeModal: () => void; + isLoading?: boolean; + onConfirmTerminate: () => void; + error?: ApiError; +}; + +export const TerminateModal: React.FC = ({ + headline, + description, + terminateInputButton, + closeModal, + isLoading, + onConfirmTerminate, + error, +}) => { + const { t } = useTranslation('key-management-service/terminate'); + const [terminateInput, setTerminateInput] = useState(''); + + const close = () => { + setTerminateInput(''); + closeModal(); + }; + + return ( + + {!!error && ( + + + {t('key_management_service_terminate_error', { + error: error.response?.data?.message, + })} + + + )} + + {description} + + + setTerminateInput(e.detail.value) + } + /> + {isLoading && } + + {t('key_management_service_terminate_cancel')} + + { + setTerminateInput(''); + onConfirmTerminate(); + }} + > + {terminateInputButton} + + + ); +}; diff --git a/packages/manager/apps/key-management-service/src/components/Modal/terminate/TerminateModal.constants.ts b/packages/manager/apps/key-management-service/src/components/Modal/terminate/TerminateModal.constants.ts new file mode 100644 index 000000000000..b12864b664ed --- /dev/null +++ b/packages/manager/apps/key-management-service/src/components/Modal/terminate/TerminateModal.constants.ts @@ -0,0 +1 @@ +export const terminateValue = 'TERMINATE'; diff --git a/packages/manager/apps/key-management-service/src/components/menu/KmsActionMenu.component.tsx b/packages/manager/apps/key-management-service/src/components/menu/KmsActionMenu.component.tsx new file mode 100644 index 000000000000..4fffd9dbfc04 --- /dev/null +++ b/packages/manager/apps/key-management-service/src/components/menu/KmsActionMenu.component.tsx @@ -0,0 +1,36 @@ +import { + ActionMenu, + ActionMenuItem, + ActionMenuLayout, +} from '@ovhcloud/manager-components'; +import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { OKMS } from '@/interface'; +import { ROUTES_URLS } from '@/routes/routes.constants'; + +const menuLayout: ActionMenuLayout = { + isCompact: true, + hasVerticalEllipsis: true, +}; + +const KmsActionMenu: React.FC = ({ id }) => { + const { t } = useTranslation('key-management-service/listing'); + const navigate = useNavigate(); + + const items: ActionMenuItem[] = [ + { + id: 1, + label: t('key_management_service_listing_terminate'), + color: ODS_THEME_COLOR_INTENT.error, + onClick: () => { + navigate(`${ROUTES_URLS.terminateOkms}/${id}`); + }, + }, + ]; + + return ; +}; + +export default KmsActionMenu; diff --git a/packages/manager/apps/key-management-service/src/index.tsx b/packages/manager/apps/key-management-service/src/index.tsx index 9c43642a1044..554b497ff2c2 100644 --- a/packages/manager/apps/key-management-service/src/index.tsx +++ b/packages/manager/apps/key-management-service/src/index.tsx @@ -24,7 +24,7 @@ const init = async (appName: string) => { context, reloadOnLocaleChange: true, defaultNS: appName, - ns: [`${appName}/listing`, `${appName}/dashboard`], + ns: [`${appName}/listing`, `${appName}/dashboard`, `${appName}/terminate`], }); ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/packages/manager/apps/key-management-service/src/pages/dashboard/index.tsx b/packages/manager/apps/key-management-service/src/pages/dashboard/index.tsx index 56710cfb550b..267367b6cb00 100644 --- a/packages/manager/apps/key-management-service/src/pages/dashboard/index.tsx +++ b/packages/manager/apps/key-management-service/src/pages/dashboard/index.tsx @@ -52,7 +52,7 @@ export default function DashboardPage() { label: tListing('key_management_service_listing_title'), }, { - href: ROUTES_URLS.dashboard, + href: `/${okmsId}`, label: okmsId, }, ]} diff --git a/packages/manager/apps/key-management-service/src/pages/listing/TerminateKms.tsx b/packages/manager/apps/key-management-service/src/pages/listing/TerminateKms.tsx new file mode 100644 index 000000000000..0c12eb7c3ce1 --- /dev/null +++ b/packages/manager/apps/key-management-service/src/pages/listing/TerminateKms.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate, useParams } from 'react-router-dom'; +import { TerminateModal } from '@/components/Modal/terminate/TerminateModal.component'; +import { useTerminateOKms } from '@/api/hooks/useTerminateOKms'; + +export default function TerminateKms() { + const { t } = useTranslation('key-management-service/terminate'); + const navigate = useNavigate(); + const { kmsId } = useParams(); + + const closeModal = () => { + navigate('..'); + }; + + const { terminateKms, isErrorVisible, error } = useTerminateOKms({ + kmsId, + onSuccess: closeModal, + }); + + return ( + + ); +} diff --git a/packages/manager/apps/key-management-service/src/pages/listing/index.tsx b/packages/manager/apps/key-management-service/src/pages/listing/index.tsx index a6a2d75bc5f7..f5b2c5b475f7 100644 --- a/packages/manager/apps/key-management-service/src/pages/listing/index.tsx +++ b/packages/manager/apps/key-management-service/src/pages/listing/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { Outlet, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { ODS_SPINNER_SIZE, @@ -28,6 +28,7 @@ import { import { useOKMS } from '@/hooks/useOKMS'; import { ROUTES_URLS } from '@/routes/routes.constants'; import { + DatagridActionMenu, DatagridCellId, DatagridCellName, DatagridCellRegion, @@ -55,6 +56,12 @@ export default function Listing() { cell: DatagridCellRegion, label: t('key_management_service_listing_region_cell'), }, + { + id: 'action', + cell: DatagridActionMenu, + isSortable: false, + label: '', + }, ]; const { sorting, setSorting } = useDatagridSearchParams(); @@ -125,6 +132,7 @@ export default function Listing() { /> )} + ); } diff --git a/packages/manager/apps/key-management-service/src/public/translations/key-management-service/listing/Messages_fr_FR.json b/packages/manager/apps/key-management-service/src/public/translations/key-management-service/listing/Messages_fr_FR.json index c8ea52d4ec15..495e9cf3ebfe 100644 --- a/packages/manager/apps/key-management-service/src/public/translations/key-management-service/listing/Messages_fr_FR.json +++ b/packages/manager/apps/key-management-service/src/public/translations/key-management-service/listing/Messages_fr_FR.json @@ -7,6 +7,7 @@ "key_management_service_listing_clipboard_error_label": "Erreur", "key_management_service_listing_add_kms_button": "Commander un KMS", "key_management_service_listing_title": "Mes KMS", + "key_management_service_listing_terminate": "Résilier", "key_management_service_listing_region_eu_west_par": "Europe (France - Paris)", "key_management_service_listing_region_eu_west_gra": "Europe (France - Gravelines)", "key_management_service_listing_region_eu_west_rbx": "Europe (France - Roubaix)", diff --git a/packages/manager/apps/key-management-service/src/public/translations/key-management-service/terminate/Messages_fr_FR.json b/packages/manager/apps/key-management-service/src/public/translations/key-management-service/terminate/Messages_fr_FR.json new file mode 100644 index 000000000000..45b8ee7f3ba8 --- /dev/null +++ b/packages/manager/apps/key-management-service/src/public/translations/key-management-service/terminate/Messages_fr_FR.json @@ -0,0 +1,7 @@ +{ + "key_management_service_terminate_cancel": "Annuler", + "key_management_service_terminate_confirm": "Résilier", + "key_management_service_terminate_error": "Une erreur est survenue. {{error}}", + "key_management_service_terminate_heading": "Résilier mon KMS", + "key_management_service_terminate_description": "Veuillez entrer le mot « TERMINATE » pour confirmer la résiliation de votre KMS" +} diff --git a/packages/manager/apps/key-management-service/src/routes/routes.constants.ts b/packages/manager/apps/key-management-service/src/routes/routes.constants.ts index a1cd450c03a7..ffb496ea71a9 100644 --- a/packages/manager/apps/key-management-service/src/routes/routes.constants.ts +++ b/packages/manager/apps/key-management-service/src/routes/routes.constants.ts @@ -2,7 +2,6 @@ export const ROUTES_URLS = { root: '/', listing: '/', onboarding: '/onboarding', + terminateOkms: '/terminate', createKeyManagementService: '/create', - dashboard: '/:id', - overview: '/:id', }; diff --git a/packages/manager/apps/key-management-service/src/routes/routes.tsx b/packages/manager/apps/key-management-service/src/routes/routes.tsx index a8bf45ed0acd..b20a3cd8483f 100644 --- a/packages/manager/apps/key-management-service/src/routes/routes.tsx +++ b/packages/manager/apps/key-management-service/src/routes/routes.tsx @@ -25,6 +25,12 @@ export default [ { path: ROUTES_URLS.listing, ...lazyRouteConfig(() => import('@/pages/listing')), + children: [ + { + path: `${ROUTES_URLS.terminateOkms}/:okmsId`, + ...lazyRouteConfig(() => import('@/pages/listing/TerminateKms')), + }, + ], }, { path: ROUTES_URLS.createKeyManagementService, diff --git a/packages/manager/modules/billing-components/src/components/cancellation-form/confirm-terminate.constants.js b/packages/manager/modules/billing-components/src/components/cancellation-form/confirm-terminate.constants.js index 5d32a545d9a7..0a4f9ab67927 100644 --- a/packages/manager/modules/billing-components/src/components/cancellation-form/confirm-terminate.constants.js +++ b/packages/manager/modules/billing-components/src/components/cancellation-form/confirm-terminate.constants.js @@ -2,7 +2,7 @@ export const SPECIAL_CONDITIONS_SUBSIDIARIES = ['US']; export const TERMINATION_FORM_NAME = 'termination'; -export const SERVICE_WITH_AGORA_TERMINATION = ['vrack-services']; +export const SERVICE_WITH_AGORA_TERMINATION = ['vrack-services', 'okms']; export default { SPECIAL_CONDITIONS_SUBSIDIARIES,