Added tags to secrets in the dashboard

This commit is contained in:
Vladyslav Matsiiako
2023-02-07 16:29:15 -08:00
parent 56da34d343
commit 7892624709
31 changed files with 695 additions and 102 deletions
@@ -18,19 +18,24 @@ import { useToggle } from '@app/hooks';
import {
useCreateServiceToken,
useCreateWsEnvironment,
useCreateWsTag,
useDeleteServiceToken,
useDeleteWorkspace,
useDeleteWsEnvironment,
useDeleteWsTag,
useGetUserWsKey,
useGetUserWsServiceTokens,
useGetWsTags,
useRenameWorkspace,
useUpdateWsEnvironment
} from '@app/hooks/api';
import { SecretTagsSection } from './components/SecretTagsSection';
import {
CopyProjectIDSection,
CreateServiceToken,
CreateUpdateEnvFormData,
CreateWsTag,
EnvironmentSection,
ProjectNameChangeSection,
ServiceTokenSection
@@ -61,6 +66,11 @@ export const ProjectSettingsPage = () => {
const createServiceToken = useCreateServiceToken();
const deleteServiceToken = useDeleteServiceToken();
// tag
const { data: wsTags } = useGetWsTags(workspaceID);
const createWsTag = useCreateWsTag();
const deleteWsTag = useDeleteWsTag();
// get user subscription
const { subscriptionPlan } = useSubscription();
const host = window.location.origin;
@@ -207,6 +217,44 @@ export const ProjectSettingsPage = () => {
return '';
};
const onCreateWsTag = async ({ name }: CreateWsTag) => {
try {
const res = await createWsTag.mutateAsync({
workspaceID,
tagName: name,
tagSlug: name.replace(" ", "_")
});
createNotification({
text: 'Successfully created a tag',
type: 'success'
});
return res?.data?.name;
} catch (error) {
console.error(error);
createNotification({
text: 'Failed to create a tag',
type: 'error'
});
}
return '';
};
const onDeleteTag = async (tagID: string) => {
try {
await deleteWsTag.mutateAsync({ tagID });
createNotification({
text: 'Successfully deleted tag',
type: 'success'
});
} catch (error) {
console.error(error);
createNotification({
text: 'Failed to delete the tag',
type: 'error'
});
}
};
const onDeleteServiceToken = async (tokenID: string) => {
try {
await deleteServiceToken.mutateAsync(tokenID);
@@ -224,7 +272,7 @@ export const ProjectSettingsPage = () => {
};
return (
<div className="container mx-auto flex flex-col px-8 text-mineshaft-50">
<div className="container mx-auto flex flex-col px-8 text-mineshaft-50 dark dark:[color-scheme:dark]">
{/* TODO(akhilmhdh): Remove this right when layout is refactored */}
<div className="relative right-5">
<NavHeader pageName={t('settings-project:title')} isProjectRelated />
@@ -256,6 +304,12 @@ export const ProjectSettingsPage = () => {
workspaceName={currentWorkspace?.name || ''}
onCreateToken={onCreateServiceToken}
/>
<SecretTagsSection
tags={wsTags || []}
onDeleteTag={onDeleteTag}
workspaceName={currentWorkspace?.name || ''}
onCreateTag={onCreateWsTag}
/>
<div className="mb-6 mt-4 flex w-full flex-col items-start rounded-md border-l border-red bg-white/5 px-6 pl-6 pb-4 pt-4">
<p className="text-xl font-bold text-red">{t('settings-project:danger-zone')}</p>
<p className="text-md mt-2 text-gray-400">{t('settings-project:danger-zone-note')}</p>
@@ -102,7 +102,7 @@ export const EnvironmentSection = ({
colorSchema="primary"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
Add New Env
Add New Environment
</Button>
</div>
</div>
@@ -0,0 +1,186 @@
import { Controller, useForm } from 'react-hook-form';
import { faPlus, faTrashCan } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
import {
Button,
DeleteActionModal,
FormControl,
IconButton,
Input,
Modal,
ModalClose,
ModalContent,
ModalTrigger,
Table,
TableContainer,
TBody,
Td,
Th,
THead,
Tr,
} from '@app/components/v2';
import { usePopUp } from '@app/hooks';
import { WorkspaceTag } from '@app/hooks/api/types';
const createTagSchema = yup.object({
name: yup.string().required().label('Tag Name'),
});
export type CreateWsTag = yup.InferType<typeof createTagSchema>;
type Props = {
tags: WorkspaceTag[];
workspaceName: string;
onDeleteTag: (tagID: string) => Promise<void>;
onCreateTag: (data: CreateWsTag) => Promise<string>;
};
type DeleteModalData = { name: string; id: string };
export const SecretTagsSection = ({
tags = [],
onDeleteTag,
workspaceName,
onCreateTag
}: Props): JSX.Element => {
const { popUp, handlePopUpToggle, handlePopUpClose, handlePopUpOpen } = usePopUp([
'CreateSecretTag',
'deleteTagConfirmation'
] as const);
const {
control,
reset,
handleSubmit,
formState: { isSubmitting }
} = useForm<CreateWsTag>({
resolver: yupResolver(createTagSchema)
});
const onFormSubmit = async (data: CreateWsTag) => {
console.log(19191, data);
await onCreateTag(data);
handlePopUpClose('CreateSecretTag');
};
const onDeleteApproved = async () => {
await onDeleteTag((popUp?.deleteTagConfirmation?.data as DeleteModalData)?.id);
handlePopUpClose('deleteTagConfirmation');
};
return (
<div className="mt-4 mb-4 flex w-full flex-col items-start rounded-md bg-white/5 p-6">
<div className="flex w-full flex-row justify-between">
<div className="flex w-full flex-col">
<p className="mb-3 text-xl font-semibold">Secret Tags</p>
<p className="text-sm text-gray-400">Every secret can be assigned to one or more tags. Here you can add and remove tags for the current project.</p>
</div>
<div>
<Modal
isOpen={popUp?.CreateSecretTag?.isOpen}
onOpenChange={(open) => {
handlePopUpToggle('CreateSecretTag', open);
reset();
}}
>
<ModalTrigger asChild>
<Button color="mineshaft" leftIcon={<FontAwesomeIcon icon={faPlus} />}>
Add New Tag
</Button>
</ModalTrigger>
<ModalContent
title={`Add a tag for ${ workspaceName}`}
subTitle='Specify your tag name, and the slug will be created automatically.'
>
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
name="name"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label='Tag Name'
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="Type your tag name" />
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
type="submit"
isDisabled={isSubmitting}
isLoading={isSubmitting}
>
Create
</Button>
<ModalClose asChild>
<Button variant="plain" colorSchema="secondary">
Cancel
</Button>
</ModalClose>
</div>
</form>
</ModalContent>
</Modal>
</div>
</div>
<TableContainer className='mt-4'>
<Table>
<THead>
<Tr>
<Th>Tag</Th>
<Th>Slug</Th>
<Th aria-label="button" />
</Tr>
</THead>
<TBody>
{tags?.length > 0 ? (
tags.map(({ _id, name, slug }) => (
<Tr key={name}>
<Td>{name}</Td>
<Td>{slug}</Td>
<Td className="flex items-center justify-end">
<IconButton
onClick={() =>
handlePopUpOpen('deleteTagConfirmation', {
name,
id: _id
})
}
colorSchema="danger"
ariaLabel="update"
>
<FontAwesomeIcon icon={faTrashCan} />
</IconButton>
</Td>
</Tr>
))
) : (
<Tr>
<Td colSpan={4} className="py-6 text-center text-bunker-400">
No tags found for this project
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
<DeleteActionModal
isOpen={popUp.deleteTagConfirmation.isOpen}
title={`Delete ${
(popUp?.deleteTagConfirmation?.data as DeleteModalData)?.name || ' '
} api key?`}
onChange={(isOpen) => handlePopUpToggle('deleteTagConfirmation', isOpen)}
deleteKey={(popUp?.deleteTagConfirmation?.data as DeleteModalData)?.name}
onClose={() => handlePopUpClose('deleteTagConfirmation')}
onDeleteApproved={onDeleteApproved}
/>
</div>
);
};
@@ -0,0 +1 @@
export {SecretTagsSection} from './SecretTagsSection'
@@ -2,5 +2,6 @@ export { CopyProjectIDSection } from './CopyProjectIDSection';
export { EnvironmentSection } from './EnvironmentSection';
export type { CreateUpdateEnvFormData } from './EnvironmentSection/EnvironmentSection';
export { ProjectNameChangeSection } from './ProjectNameChangeSection';
export type { CreateWsTag } from './SecretTagsSection/SecretTagsSection';
export { ServiceTokenSection } from './ServiceTokenSection';
export type { CreateServiceToken } from './ServiceTokenSection/ServiceTokenSection';