Skip to content

Commit

Permalink
feat(key-management-service): add kms termination
Browse files Browse the repository at this point in the history
ref: MANAGER-13980

Signed-off-by: David Arsène <david.arsene.ext@ovhcloud.com>
  • Loading branch information
darsene committed May 24, 2024
1 parent ff4077c commit bc83a95
Show file tree
Hide file tree
Showing 17 changed files with 328 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,58 @@ export interface ActionMenuItem {
download?: string;
href?: string;
target?: OdsHTMLAnchorElementTarget;
color?: ODS_THEME_COLOR_INTENT;
onClick?: () => void;
label: ReactI18NextChild | Iterable<ReactI18NextChild>;
}

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<ActionMenuProps> = ({ 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<ActionMenuProps> = ({
items,
isCompact,
layout,
}) => {
const { t } = useTranslation('buttons');
const menuLayout: ActionMenuLayout = {
...layout,
isCompact: isCompact || layout?.isCompact,
};
const odsIconProps = computeOdsIconProps(menuLayout);

return (
<OsdsMenu>
<OsdsButton
Expand All @@ -46,28 +87,19 @@ export const ActionMenu: React.FC<ActionMenuProps> = ({ items, isCompact }) => {
type={ODS_BUTTON_TYPE.button}
size={ODS_BUTTON_SIZE.sm}
inline
circle={isCompact || undefined}
circle={menuLayout.isCompact || undefined}
>
{!isCompact && t('common_actions')}
<span slot={!isCompact ? 'end' : undefined}>
<OsdsIcon
name={
isCompact
? ODS_ICON_NAME.ELLIPSIS
: ODS_ICON_NAME.ARROW_DOWN_CONCEPT
}
color={ODS_THEME_COLOR_INTENT.primary}
size={isCompact ? ODS_ICON_SIZE.xs : ODS_ICON_SIZE.xxs}
data-testid="action-menu-icon"
/>
{!menuLayout.isCompact && t('common_actions')}
<span slot={!menuLayout.isCompact ? 'end' : undefined}>
<OsdsIcon {...odsIconProps} data-testid="action-menu-icon" />
</span>
</OsdsButton>

{items.map((item) => (
<OsdsMenuItem key={item.id}>
<OsdsButton
size={ODS_BUTTON_SIZE.sm}
color={ODS_THEME_COLOR_INTENT.primary}
color={item.color ?? ODS_THEME_COLOR_INTENT.primary}
variant={ODS_BUTTON_VARIANT.ghost}
href={item.href}
rel={item.rel}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { ApiError, ApiResponse } from '@ovh-ux/manager-core-api';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import React from 'react';
import { terminateOKmsQueryKey, terminateOKms } from '../services/post';
import { getOkmsServiceId, getOkmsServiceIdQueryKey } from '../services/get';
import { getOkmsServicesResourceListQueryKey } from '../GET/apiv2/services';

export const useTerminateOKms = ({
kmsId,
onSuccess,
onError,
}: {
kmsId: string;
onSuccess?: () => 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<number[]>
>({
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),
};
};
Original file line number Diff line number Diff line change
@@ -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`);
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -41,3 +42,7 @@ export const DatagridCellRegion = (props: OKMS) => {
export const DatagridCellRestApiEndpoint = (props: OKMS) => {
return <OsdsLink href={props?.restEndpoint}>{props?.restEndpoint}</OsdsLink>;
};

export const DatagridActionMenu = (props: OKMS) => {
return <KmsActionMenu {...props} />;
};
Original file line number Diff line number Diff line change
@@ -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<TerminateModalProps> = ({
headline,
description,
terminateInputButton,
closeModal,
isLoading,
onConfirmTerminate,
error,
}) => {
const { t } = useTranslation('key-management-service/terminate');
const [terminateInput, setTerminateInput] = useState('');

const close = () => {
setTerminateInput('');
closeModal();
};

return (
<OsdsModal
dismissible
color={ODS_THEME_COLOR_INTENT.warning}
headline={headline}
onOdsModalClose={close}
>
{!!error && (
<OsdsMessage type={ODS_MESSAGE_TYPE.error}>
<OsdsText
level={ODS_TEXT_LEVEL.body}
size={ODS_TEXT_SIZE._400}
color={ODS_THEME_COLOR_INTENT.text}
>
{t('key_management_service_terminate_error', {
error: error.response?.data?.message,
})}
</OsdsText>
</OsdsMessage>
)}
<OsdsText
color={ODS_THEME_COLOR_INTENT.text}
level={ODS_TEXT_LEVEL.body}
className="block mb-3"
>
{description}
</OsdsText>
<OsdsInput
disabled={isLoading || undefined}
type={ODS_INPUT_TYPE.text}
value={terminateInput}
onOdsValueChange={(e: OdsInputValueChangeEvent) =>
setTerminateInput(e.detail.value)
}
/>
{isLoading && <OsdsSpinner inline size={ODS_SPINNER_SIZE.md} />}
<OsdsButton
disabled={isLoading || undefined}
slot="actions"
type={ODS_BUTTON_TYPE.button}
variant={ODS_BUTTON_VARIANT.ghost}
color={ODS_THEME_COLOR_INTENT.primary}
onClick={close}
>
{t('key_management_service_terminate_cancel')}
</OsdsButton>
<OsdsButton
disabled={isLoading || terminateInput !== terminateValue || undefined}
slot="actions"
type={ODS_BUTTON_TYPE.button}
variant={ODS_BUTTON_VARIANT.flat}
color={ODS_THEME_COLOR_INTENT.primary}
onClick={() => {
setTerminateInput('');
onConfirmTerminate();
}}
>
{terminateInputButton}
</OsdsButton>
</OsdsModal>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const terminateValue = 'TERMINATE';
Original file line number Diff line number Diff line change
@@ -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<OKMS> = ({ 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 <ActionMenu items={items} layout={menuLayout} />;
};

export default KmsActionMenu;
2 changes: 1 addition & 1 deletion packages/manager/apps/key-management-service/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default function DashboardPage() {
label: tListing('key_management_service_listing_title'),
},
{
href: ROUTES_URLS.dashboard,
href: `/${okmsId}`,
label: okmsId,
},
]}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<TerminateModal
headline={t('key_management_service_terminate_heading')}
terminateInputButton={t('key_management_service_terminate_confirm')}
description={t('key_management_service_terminate_description')}
onConfirmTerminate={terminateKms}
closeModal={closeModal}
error={isErrorVisible ? error : null}
/>
);
}

0 comments on commit bc83a95

Please sign in to comment.