mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 18:15:53 +00:00
feat: renamed old frontend and fixed some minor bugs
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { ShareSecretSection } from "./components";
|
||||
|
||||
export const SecretSharingPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: t("approval.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content={String(t("approval.og-title"))} />
|
||||
<meta name="og:description" content={String(t("approval.og-description"))} />
|
||||
</Helmet>
|
||||
<div className="h-full">
|
||||
<div className="container mx-auto h-full w-full max-w-7xl bg-bunker-800 px-6 text-white">
|
||||
<div className="flex items-center justify-between py-6">
|
||||
<div className="flex w-full flex-col">
|
||||
<h2 className="text-3xl font-semibold text-gray-200">Secret Sharing</h2>
|
||||
<p className="text-bunker-300">Share secrets securely using a shareable link</p>
|
||||
</div>
|
||||
<div className="flex w-max justify-center">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://infisical.com/docs/documentation/platform/secret-sharing"
|
||||
>
|
||||
<div className="flex w-max cursor-pointer items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
|
||||
Documentation{" "}
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="mb-[0.06rem] ml-1 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<ShareSecretSection />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
import { ShareSecretForm } from "@app/pages/public/ShareSecretPage/components";
|
||||
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["createSharedSecret"]>;
|
||||
handlePopUpToggle: (
|
||||
popUpName: keyof UsePopUpState<["createSharedSecret"]>,
|
||||
state?: boolean
|
||||
) => void;
|
||||
};
|
||||
|
||||
export const AddShareSecretModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.createSharedSecret?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("createSharedSecret", isOpen);
|
||||
}}
|
||||
>
|
||||
<ModalContent
|
||||
title="Share a Secret"
|
||||
subTitle="Securely share one off secrets with your team."
|
||||
>
|
||||
<ShareSecretForm
|
||||
isPublic={false}
|
||||
value={(popUp.createSharedSecret.data as { value?: string })?.value}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteSharedSecret } from "@app/hooks/api/secretSharing";
|
||||
|
||||
import { AddShareSecretModal } from "./AddShareSecretModal";
|
||||
import { ShareSecretsTable } from "./ShareSecretsTable";
|
||||
|
||||
type DeleteModalData = { name: string; id: string };
|
||||
|
||||
export const ShareSecretSection = () => {
|
||||
const deleteSharedSecret = useDeleteSharedSecret();
|
||||
const { popUp, handlePopUpToggle, handlePopUpClose, handlePopUpOpen } = usePopUp([
|
||||
"createSharedSecret",
|
||||
"deleteSharedSecretConfirmation"
|
||||
] as const);
|
||||
|
||||
const onDeleteApproved = async () => {
|
||||
try {
|
||||
deleteSharedSecret.mutateAsync({
|
||||
sharedSecretId: (popUp?.deleteSharedSecretConfirmation?.data as DeleteModalData)?.id
|
||||
});
|
||||
createNotification({
|
||||
text: "Successfully deleted shared secret",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
handlePopUpClose("deleteSharedSecretConfirmation");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to delete shared secret",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<Helmet>
|
||||
<title>Secret Sharing</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Helmet>
|
||||
<div className="mb-4 flex justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Shared Secrets</p>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => {
|
||||
handlePopUpOpen("createSharedSecret");
|
||||
}}
|
||||
>
|
||||
Share Secret
|
||||
</Button>
|
||||
</div>
|
||||
<ShareSecretsTable handlePopUpOpen={handlePopUpOpen} />
|
||||
<AddShareSecretModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteSharedSecretConfirmation.isOpen}
|
||||
title={`Delete ${
|
||||
(popUp?.deleteSharedSecretConfirmation?.data as DeleteModalData)?.name || " "
|
||||
} shared secret?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteSharedSecretConfirmation", isOpen)}
|
||||
deleteKey={(popUp?.deleteSharedSecretConfirmation?.data as DeleteModalData)?.name}
|
||||
onClose={() => handlePopUpClose("deleteSharedSecretConfirmation")}
|
||||
onDeleteApproved={onDeleteApproved}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
import { faEnvelope, faEnvelopeOpen, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
|
||||
import { IconButton, Td, Tooltip, Tr } from "@app/components/v2";
|
||||
import { Badge } from "@app/components/v2/Badge";
|
||||
import { TSharedSecret } from "@app/hooks/api/secretSharing";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
export const ShareSecretsRow = ({
|
||||
row,
|
||||
handlePopUpOpen
|
||||
}: {
|
||||
row: TSharedSecret;
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["deleteSharedSecretConfirmation"]>,
|
||||
{
|
||||
name,
|
||||
id
|
||||
}: {
|
||||
name: string;
|
||||
id: string;
|
||||
}
|
||||
) => 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 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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import { useState } from "react";
|
||||
import { faKey } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import {
|
||||
EmptyState,
|
||||
Pagination,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Th,
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { useGetSharedSecrets } from "@app/hooks/api/secretSharing";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
import { ShareSecretsRow } from "./ShareSecretsRow";
|
||||
|
||||
type Props = {
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["deleteSharedSecretConfirmation"]>,
|
||||
{
|
||||
name,
|
||||
id
|
||||
}: {
|
||||
name: string;
|
||||
id: string;
|
||||
}
|
||||
) => void;
|
||||
};
|
||||
|
||||
export const ShareSecretsTable = ({ handlePopUpOpen }: Props) => {
|
||||
const [page, setPage] = useState(1);
|
||||
const [perPage, setPerPage] = useState(10);
|
||||
const { isPending, data } = useGetSharedSecrets({
|
||||
offset: (page - 1) * perPage,
|
||||
limit: perPage
|
||||
});
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<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>
|
||||
{isPending && <TableSkeleton columns={7} innerKey="shared-secrets" />}
|
||||
{!isPending &&
|
||||
data?.secrets?.map((row) => (
|
||||
<ShareSecretsRow key={row.id} row={row} handlePopUpOpen={handlePopUpOpen} />
|
||||
))}
|
||||
</TBody>
|
||||
</Table>
|
||||
{!isPending &&
|
||||
data?.secrets &&
|
||||
data?.totalCount >= perPage &&
|
||||
data?.totalCount !== undefined && (
|
||||
<Pagination
|
||||
count={data.totalCount}
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
onChangePage={(newPage) => setPage(newPage)}
|
||||
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
|
||||
/>
|
||||
)}
|
||||
{!isPending && !data?.secrets?.length && (
|
||||
<EmptyState title="No secrets shared yet" icon={faKey} />
|
||||
)}
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { ShareSecretSection } from "./ShareSecretSection";
|
||||
@@ -0,0 +1,9 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { SecretSharingPage } from "./SecretSharingPage";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
"/_authenticate/_inject-org-details/organization/_layout/secret-sharing"
|
||||
)({
|
||||
component: SecretSharingPage
|
||||
});
|
||||
Reference in New Issue
Block a user