mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 20:25:43 +00:00
Continue secret sharing
This commit is contained in:
@@ -88,7 +88,6 @@ export const AddShareSecretForm = ({
|
||||
encryptedValue: ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
hashedHex,
|
||||
expiresAt,
|
||||
expiresAfterViews: expiresAfterViews === "-1" ? undefined : Number(expiresAfterViews),
|
||||
accessType: accessType as SecretSharingAccessType
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import { useTimedReset } from "@app/hooks";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
import { AddShareSecretForm } from "./AddShareSecretForm";
|
||||
import { ViewAndCopySharedSecret } from "./ViewAndCopySharedSecret";
|
||||
|
||||
const schema = yup.object({
|
||||
value: yup.string().max(10000).required().label("Shared Secret Value"),
|
||||
expiresInValue: yup.string().required().label("Expiration Value"),
|
||||
expiresAfterViews: yup.string().required().label("Expires After Views")
|
||||
});
|
||||
|
||||
export type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["createSharedSecret"]>;
|
||||
handlePopUpToggle: (
|
||||
popUpName: keyof UsePopUpState<["createSharedSecret"]>,
|
||||
state?: boolean
|
||||
) => void;
|
||||
isPublic: boolean;
|
||||
inModal: boolean;
|
||||
};
|
||||
|
||||
export const AddShareSecretModal = ({ popUp, handlePopUpToggle, isPublic, inModal }: Props) => {
|
||||
const {
|
||||
control,
|
||||
reset,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
|
||||
const [newSharedSecret, setNewSharedSecret] = useState("");
|
||||
const hasSharedSecret = Boolean(newSharedSecret);
|
||||
const [isUrlCopied, , setIsUrlCopied] = useTimedReset<boolean>({
|
||||
initialState: false
|
||||
});
|
||||
|
||||
const [isSecretInputDisabled, setIsSecretInputDisabled] = useState(false);
|
||||
|
||||
const copyUrlToClipboard = () => {
|
||||
navigator.clipboard.writeText(newSharedSecret);
|
||||
setIsUrlCopied(true);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (isUrlCopied) {
|
||||
setTimeout(() => setIsUrlCopied(false), 2000);
|
||||
}
|
||||
}, [isUrlCopied]);
|
||||
|
||||
useEffect(() => {
|
||||
if (popUp.createSharedSecret.data) {
|
||||
setValue("value", (popUp.createSharedSecret.data as { value: string }).value);
|
||||
setIsSecretInputDisabled(true);
|
||||
}
|
||||
}, [popUp.createSharedSecret.data]);
|
||||
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
return inModal ? (
|
||||
<Modal
|
||||
isOpen={popUp?.createSharedSecret?.isOpen}
|
||||
onOpenChange={(open) => {
|
||||
handlePopUpToggle("createSharedSecret", open);
|
||||
reset();
|
||||
setNewSharedSecret("");
|
||||
setIsSecretInputDisabled(false);
|
||||
}}
|
||||
>
|
||||
<ModalContent
|
||||
title="Share a Secret"
|
||||
subTitle="Once you share a secret, the share link is only accessible once."
|
||||
>
|
||||
{!hasSharedSecret ? (
|
||||
<AddShareSecretForm
|
||||
isPublic={isPublic}
|
||||
inModal={inModal}
|
||||
control={control}
|
||||
handleSubmit={handleSubmit}
|
||||
isSubmitting={isSubmitting}
|
||||
setNewSharedSecret={setNewSharedSecret}
|
||||
isInputDisabled={isSecretInputDisabled}
|
||||
/>
|
||||
) : (
|
||||
<ViewAndCopySharedSecret
|
||||
inModal={inModal}
|
||||
newSharedSecret={newSharedSecret}
|
||||
isUrlCopied={isUrlCopied}
|
||||
copyUrlToClipboard={copyUrlToClipboard}
|
||||
/>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
) : !hasSharedSecret ? (
|
||||
<AddShareSecretForm
|
||||
isPublic={isPublic}
|
||||
inModal={inModal}
|
||||
control={control}
|
||||
handleSubmit={handleSubmit}
|
||||
isSubmitting={isSubmitting}
|
||||
setNewSharedSecret={setNewSharedSecret}
|
||||
isInputDisabled={isSecretInputDisabled}
|
||||
/>
|
||||
) : (
|
||||
<ViewAndCopySharedSecret
|
||||
inModal={inModal}
|
||||
newSharedSecret={newSharedSecret}
|
||||
isUrlCopied={isUrlCopied}
|
||||
copyUrlToClipboard={copyUrlToClipboard}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
import { ShareSecretForm } from "@app/views/ShareSecretPublicPage/components";
|
||||
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["createSharedSecret"]>;
|
||||
handlePopUpToggle: (
|
||||
popUpName: keyof UsePopUpState<["createSharedSecret"]>,
|
||||
state?: boolean
|
||||
) => void;
|
||||
};
|
||||
|
||||
export const AddShareSecretModal2 = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.createSharedSecret?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("createSharedSecret", isOpen);
|
||||
}}
|
||||
>
|
||||
<ModalContent
|
||||
title="Share a Secret"
|
||||
subTitle="Once you share a secret, the share link is only accessible once."
|
||||
>
|
||||
<ShareSecretForm
|
||||
isPublic={false}
|
||||
value={(popUp.createSharedSecret.data as { value?: string })?.value}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -7,7 +7,7 @@ import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteSharedSecret } from "@app/hooks/api/secretSharing";
|
||||
|
||||
import { AddShareSecretModal } from "./AddShareSecretModal";
|
||||
import { AddShareSecretModal2 } from "./AddShareSecretModal2";
|
||||
import { ShareSecretsTable } from "./ShareSecretsTable";
|
||||
|
||||
type DeleteModalData = { name: string; id: string };
|
||||
@@ -59,12 +59,7 @@ export const ShareSecretSection = () => {
|
||||
</Button>
|
||||
</div>
|
||||
<ShareSecretsTable handlePopUpOpen={handlePopUpOpen} />
|
||||
<AddShareSecretModal
|
||||
popUp={popUp}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
isPublic={false}
|
||||
inModal
|
||||
/>
|
||||
<AddShareSecretModal2 popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteSharedSecretConfirmation.isOpen}
|
||||
title={`Delete ${
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faEnvelope, faEnvelopeOpen, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
|
||||
import { IconButton, Td, Tr } from "@app/components/v2";
|
||||
import { IconButton, Td, Tooltip, Tr } from "@app/components/v2";
|
||||
// import { useToggle } from "@app/hooks";
|
||||
import { Badge } from "@app/components/v2/Badge";
|
||||
import { TSharedSecret } from "@app/hooks/api/secretSharing";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
@@ -22,26 +24,71 @@ export const ShareSecretsRow = ({
|
||||
}
|
||||
) => void;
|
||||
}) => {
|
||||
// const [isRowExpanded, setIsRowExpanded] = useToggle();
|
||||
const lastViewedAt = row.lastViewedAt
|
||||
? format(new Date(row.lastViewedAt), "yyyy-MM-dd - HH:mm a")
|
||||
: undefined;
|
||||
|
||||
let isExpired = false;
|
||||
if (row.expiresAfterViews !== null && row.expiresAfterViews <= 0) {
|
||||
isExpired = true;
|
||||
}
|
||||
|
||||
if (row.expiresAt !== null && new Date(row.expiresAt) < new Date()) {
|
||||
isExpired = true;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tr key={row.id} className="h-10">
|
||||
<Td>{`${row.encryptedValue.substring(0, 5)}...`}</Td>
|
||||
<Td>{format(new Date(row.createdAt), "yyyy-MM-dd - HH:mm a")}</Td>
|
||||
<Td>{format(new Date(row.expiresAt), "yyyy-MM-dd - HH:mm a")}</Td>
|
||||
<Td>{row.expiresAfterViews ? row.expiresAfterViews : "-"}</Td>
|
||||
<Td>
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
handlePopUpOpen("deleteSharedSecretConfirmation", {
|
||||
name: "delete",
|
||||
id: row.id
|
||||
})
|
||||
}
|
||||
variant="plain"
|
||||
ariaLabel="delete"
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
</Td>
|
||||
</Tr>
|
||||
<>
|
||||
<Tr
|
||||
key={row.id}
|
||||
// className="h-10 cursor-pointer transition-colors duration-300 hover:bg-mineshaft-700"
|
||||
// onClick={() => setIsRowExpanded.toggle()}
|
||||
>
|
||||
<Td>
|
||||
<Tooltip content={lastViewedAt ? `Last opened at ${lastViewedAt}` : "Not yet opened"}>
|
||||
<FontAwesomeIcon icon={lastViewedAt ? faEnvelopeOpen : faEnvelope} />
|
||||
</Tooltip>
|
||||
</Td>
|
||||
<Td>{row.name ? `${row.name}` : "-"}</Td>
|
||||
<Td>
|
||||
<Badge variant={isExpired ? "danger" : "success"}>
|
||||
{isExpired ? "Expired" : "Active"}
|
||||
</Badge>
|
||||
</Td>
|
||||
<Td>{`${format(new Date(row.createdAt), "yyyy-MM-dd - HH:mm a")}`}</Td>
|
||||
<Td>{format(new Date(row.expiresAt), "yyyy-MM-dd - HH:mm a")}</Td>
|
||||
<Td>{row.expiresAfterViews !== null ? row.expiresAfterViews : "-"}</Td>
|
||||
<Td>
|
||||
<IconButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("deleteSharedSecretConfirmation", {
|
||||
name: "delete",
|
||||
id: row.id
|
||||
});
|
||||
}}
|
||||
variant="plain"
|
||||
ariaLabel="delete"
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
</Td>
|
||||
</Tr>
|
||||
{/* {isRowExpanded && (
|
||||
<Tr>
|
||||
<Td
|
||||
colSpan={6}
|
||||
className={`bg-bunker-600 px-0 py-0 ${isRowExpanded && " border-mineshaft-500 p-8"}`}
|
||||
>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>Test 1</div>
|
||||
<div>Test 2</div>
|
||||
<div>Test 3</div>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useState } from "react";
|
||||
import { faKey } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import {
|
||||
EmptyState,
|
||||
Pagination,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr
|
||||
@@ -30,34 +31,46 @@ type Props = {
|
||||
};
|
||||
|
||||
export const ShareSecretsTable = ({ handlePopUpOpen }: Props) => {
|
||||
const { isLoading, data = [] } = useGetSharedSecrets();
|
||||
const [page, setPage] = useState(1);
|
||||
const [perPage, setPerPage] = useState(10);
|
||||
const { isLoading, data } = useGetSharedSecrets({
|
||||
offset: (page - 1) * perPage,
|
||||
limit: perPage
|
||||
});
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Encrypted Secret</Th>
|
||||
<Th>Created</Th>
|
||||
<Th className="w-5" />
|
||||
<Th>Name</Th>
|
||||
<Th>Status</Th>
|
||||
<Th>Created At</Th>
|
||||
<Th>Valid Until</Th>
|
||||
<Th>Views Left</Th>
|
||||
<Th aria-label="button" className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isLoading && <TableSkeleton columns={4} innerKey="shared-secrets" />}
|
||||
{isLoading && <TableSkeleton columns={7} innerKey="shared-secrets" />}
|
||||
{!isLoading &&
|
||||
data?.map((row) => (
|
||||
data?.secrets?.map((row) => (
|
||||
<ShareSecretsRow key={row.id} row={row} handlePopUpOpen={handlePopUpOpen} />
|
||||
))}
|
||||
{!isLoading && data?.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={4} className="bg-mineshaft-800 text-center text-bunker-400">
|
||||
<EmptyState title="No secrets shared yet" icon={faKey} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</TBody>
|
||||
</Table>
|
||||
{!isLoading && data?.totalCount !== undefined && (
|
||||
<Pagination
|
||||
count={data.totalCount}
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
onChangePage={(newPage) => setPage(newPage)}
|
||||
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
|
||||
/>
|
||||
)}
|
||||
{!isLoading && !data?.secrets?.length && (
|
||||
<EmptyState title="No secrets shared yet" icon={faKey} />
|
||||
)}
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user