mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #2185 from Infisical/secret-sharing
Secret Sharing UI/UX Adjustment
This commit is contained in:
@@ -95,7 +95,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
||||
tag: z.string(),
|
||||
hashedHex: z.string(),
|
||||
expiresAt: z.string(),
|
||||
expiresAfterViews: z.number()
|
||||
expiresAfterViews: z.number().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@@ -110,7 +110,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
||||
iv,
|
||||
tag,
|
||||
hashedHex,
|
||||
expiresAt: new Date(expiresAt),
|
||||
expiresAt,
|
||||
expiresAfterViews,
|
||||
accessType: SecretSharingAccessType.Anyone
|
||||
});
|
||||
@@ -131,7 +131,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
||||
tag: z.string(),
|
||||
hashedHex: z.string(),
|
||||
expiresAt: z.string(),
|
||||
expiresAfterViews: z.number(),
|
||||
expiresAfterViews: z.number().optional(),
|
||||
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization)
|
||||
}),
|
||||
response: {
|
||||
@@ -153,7 +153,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
||||
iv,
|
||||
tag,
|
||||
hashedHex,
|
||||
expiresAt: new Date(expiresAt),
|
||||
expiresAt,
|
||||
expiresAfterViews,
|
||||
accessType: req.body.accessType
|
||||
});
|
||||
|
||||
@@ -64,12 +64,13 @@ export const secretSharingServiceFactory = ({
|
||||
iv,
|
||||
tag,
|
||||
hashedHex,
|
||||
expiresAt,
|
||||
expiresAt: new Date(expiresAt),
|
||||
expiresAfterViews,
|
||||
userId: actorId,
|
||||
orgId,
|
||||
accessType
|
||||
});
|
||||
|
||||
return { id: newSharedSecret.id };
|
||||
};
|
||||
|
||||
@@ -97,7 +98,7 @@ export const secretSharingServiceFactory = ({
|
||||
iv,
|
||||
tag,
|
||||
hashedHex,
|
||||
expiresAt,
|
||||
expiresAt: new Date(expiresAt),
|
||||
expiresAfterViews,
|
||||
accessType
|
||||
});
|
||||
|
||||
@@ -16,8 +16,8 @@ export type TCreatePublicSharedSecretDTO = {
|
||||
iv: string;
|
||||
tag: string;
|
||||
hashedHex: string;
|
||||
expiresAt: Date;
|
||||
expiresAfterViews: number;
|
||||
expiresAt: string;
|
||||
expiresAfterViews?: number;
|
||||
accessType: SecretSharingAccessType;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export type TCreateSharedSecretRequest = {
|
||||
tag: string;
|
||||
hashedHex: string;
|
||||
expiresAt: Date;
|
||||
expiresAfterViews: number;
|
||||
expiresAfterViews?: number;
|
||||
accessType: SecretSharingAccessType;
|
||||
};
|
||||
|
||||
@@ -31,4 +31,4 @@ export type TDeleteSharedSecretRequest = {
|
||||
export enum SecretSharingAccessType {
|
||||
Anyone = "anyone",
|
||||
Organization = "organization"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,19 +7,38 @@ import * as yup from "yup";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { encryptSymmetric } from "@app/components/utilities/cryptography/crypto";
|
||||
import { Button, Checkbox, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2";
|
||||
import { SecretSharingAccessType, useCreatePublicSharedSecret, useCreateSharedSecret } from "@app/hooks/api/secretSharing";
|
||||
import { Button, FormControl, ModalClose, Select, SelectItem } from "@app/components/v2";
|
||||
import {
|
||||
SecretSharingAccessType,
|
||||
useCreatePublicSharedSecret,
|
||||
useCreateSharedSecret
|
||||
} from "@app/hooks/api/secretSharing";
|
||||
|
||||
const schema = yup.object({
|
||||
value: yup.string().max(10000).required().label("Shared Secret Value"),
|
||||
expiresAfterSingleView: yup.boolean().required().label("Expires After Views"),
|
||||
expiresInValue: yup.number().min(1).required().label("Expiration Value"),
|
||||
expiresInUnit: yup.string().required().label("Expiration Unit"),
|
||||
expiresAfterViews: yup.string().required().label("Expires After Views"),
|
||||
expiresInValue: yup.string().min(1).required().label("Expiration Value"),
|
||||
accessType: yup.string().required().label("General Access")
|
||||
});
|
||||
|
||||
export type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
// values in ms
|
||||
const expiresInOptions = [
|
||||
{ label: "5 min", value: 5 * 60 * 1000 },
|
||||
{ label: "30 min", value: 30 * 60 * 1000 },
|
||||
{ label: "1 hour", value: 60 * 60 * 1000 },
|
||||
{ label: "1 day", value: 24 * 60 * 60 * 1000 },
|
||||
{ label: "7 days", value: 7 * 24 * 60 * 60 * 1000 },
|
||||
{ label: "14 days", value: 14 * 24 * 60 * 60 * 1000 },
|
||||
{ label: "30 days", value: 30 * 24 * 60 * 60 * 1000 }
|
||||
];
|
||||
|
||||
const viewLimitOptions = [
|
||||
{ label: "1", value: 1 },
|
||||
{ label: "Unlimited", value: -1 }
|
||||
];
|
||||
|
||||
export const AddShareSecretForm = ({
|
||||
isPublic,
|
||||
inModal,
|
||||
@@ -49,36 +68,15 @@ export const AddShareSecretForm = ({
|
||||
const privateSharedSecretCreator = useCreateSharedSecret();
|
||||
const createSharedSecret = isPublic ? publicSharedSecretCreator : privateSharedSecretCreator;
|
||||
|
||||
const expirationUnitsAndActions = [
|
||||
{
|
||||
unit: "Minutes",
|
||||
action: (expiresAt: Date, expiresInValue: number) =>
|
||||
expiresAt.setMinutes(expiresAt.getMinutes() + expiresInValue)
|
||||
},
|
||||
{
|
||||
unit: "Hours",
|
||||
action: (expiresAt: Date, expiresInValue: number) =>
|
||||
expiresAt.setHours(expiresAt.getHours() + expiresInValue)
|
||||
},
|
||||
{
|
||||
unit: "Days",
|
||||
action: (expiresAt: Date, expiresInValue: number) =>
|
||||
expiresAt.setDate(expiresAt.getDate() + expiresInValue)
|
||||
},
|
||||
{
|
||||
unit: "Weeks",
|
||||
action: (expiresAt: Date, expiresInValue: number) =>
|
||||
expiresAt.setDate(expiresAt.getDate() + expiresInValue * 7)
|
||||
}
|
||||
];
|
||||
const onFormSubmit = async ({
|
||||
value,
|
||||
expiresInValue,
|
||||
expiresInUnit,
|
||||
expiresAfterSingleView,
|
||||
expiresAfterViews,
|
||||
accessType
|
||||
}: FormData) => {
|
||||
try {
|
||||
const expiresAt = new Date(new Date().getTime() + Number(expiresInValue));
|
||||
|
||||
const key = crypto.randomBytes(16).toString("hex");
|
||||
const hashedHex = crypto.createHash("sha256").update(key).digest("hex");
|
||||
const { ciphertext, iv, tag } = encryptSymmetric({
|
||||
@@ -86,21 +84,13 @@ export const AddShareSecretForm = ({
|
||||
key
|
||||
});
|
||||
|
||||
const expiresAt = new Date();
|
||||
const updateExpiresAt = expirationUnitsAndActions.find(
|
||||
(item) => item.unit === expiresInUnit
|
||||
)?.action;
|
||||
if (updateExpiresAt && expiresInValue) {
|
||||
updateExpiresAt(expiresAt, expiresInValue);
|
||||
}
|
||||
|
||||
const { id } = await createSharedSecret.mutateAsync({
|
||||
encryptedValue: ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
hashedHex,
|
||||
expiresAt,
|
||||
expiresAfterViews: expiresAfterSingleView ? 1 : 1000,
|
||||
expiresAfterViews: expiresAfterViews === "-1" ? undefined : Number(expiresAfterViews),
|
||||
accessType: accessType as SecretSharingAccessType
|
||||
});
|
||||
|
||||
@@ -132,9 +122,14 @@ export const AddShareSecretForm = ({
|
||||
}
|
||||
};
|
||||
return (
|
||||
<form className="flex w-full flex-col items-center px-4 sm:px-0" onSubmit={handleSubmit(onFormSubmit)}>
|
||||
<form
|
||||
className="flex w-full max-w-7xl flex-col items-center"
|
||||
onSubmit={handleSubmit(onFormSubmit)}
|
||||
>
|
||||
<div
|
||||
className={`w-full ${!inModal && "rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6"}`}
|
||||
className={`w-full ${
|
||||
!inModal && "rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6"
|
||||
}`}
|
||||
>
|
||||
<div>
|
||||
<Controller
|
||||
@@ -142,7 +137,7 @@ export const AddShareSecretForm = ({
|
||||
name="value"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Shared Secret"
|
||||
label="Your Secret"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
className="mb-2"
|
||||
@@ -157,90 +152,48 @@ export const AddShareSecretForm = ({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full flex-col md:flex-row justify-stretch">
|
||||
<div className="flex justify-start">
|
||||
<div className="flex justify-start">
|
||||
<div className="flex w-full justify-center pr-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="expiresInValue"
|
||||
defaultValue={1}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Expires after Time"
|
||||
isError={Boolean(error)}
|
||||
errorText="Please enter a valid time duration"
|
||||
className="w-32"
|
||||
>
|
||||
<Input {...field} type="number" min={0} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Controller
|
||||
control={control}
|
||||
name="expiresInUnit"
|
||||
defaultValue={expirationUnitsAndActions[1].unit}
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl label="Unit" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full border border-mineshaft-600"
|
||||
>
|
||||
{expirationUnitsAndActions.map(({ unit }) => (
|
||||
<SelectItem value={unit} key={unit}>
|
||||
{unit}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sm:w-1/7 mx-auto items-center justify-center hidden md:flex">
|
||||
<p className="mt-2 text-sm text-gray-400">AND</p>
|
||||
</div>
|
||||
<div className="items-center pb-4 md:pb-0 md:pt-2 flex">
|
||||
<Controller
|
||||
control={control}
|
||||
name="expiresAfterViews"
|
||||
defaultValue={1}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className="mb-4 w-full hidden"
|
||||
label="Expires after Views"
|
||||
isError={Boolean(error)}
|
||||
errorText="Please enter a valid number of views"
|
||||
>
|
||||
<Input {...field} type="number" min={1} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="bg-mineshaft-900 py-2 h-max rounded-md border border-mineshaft-600 px-4">
|
||||
<Controller
|
||||
control={control}
|
||||
name="expiresAfterSingleView"
|
||||
defaultValue={false}
|
||||
render={({ field: { onBlur, value, onChange } }) => (
|
||||
<Checkbox
|
||||
id="is-single-view"
|
||||
isChecked={value}
|
||||
onCheckedChange={onChange}
|
||||
isDisabled={false}
|
||||
onBlur={onBlur}
|
||||
>
|
||||
Can be viewed only 1 time
|
||||
</Checkbox>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="expiresInValue"
|
||||
defaultValue="3600000"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl label="Expires In" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{expiresInOptions.map(({ label, value }) => (
|
||||
<SelectItem value={String(value || "")} key={label}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="expiresAfterViews"
|
||||
defaultValue="-1"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl label="Max Views" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{viewLimitOptions.map(({ label, value }) => (
|
||||
<SelectItem value={String(value || "")} key={label}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{!isPublic && (
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -248,10 +201,7 @@ export const AddShareSecretForm = ({
|
||||
defaultValue="organization"
|
||||
render={({ field: { onChange, ...field } }) => (
|
||||
<FormControl label="General Access">
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
>
|
||||
<Select {...field} onValueChange={(e) => onChange(e)} className="w-full">
|
||||
<SelectItem value="organization">People within your organization</SelectItem>
|
||||
<SelectItem value="anyone">Anyone</SelectItem>
|
||||
</Select>
|
||||
@@ -261,7 +211,7 @@ export const AddShareSecretForm = ({
|
||||
)}
|
||||
<div className={`flex items-center space-x-4 pt-2 ${!inModal && ""}`}>
|
||||
<Button className="mr-0" type="submit" isDisabled={isSubmitting} isLoading={isSubmitting}>
|
||||
{inModal ? "Create" : "Share Secret"}
|
||||
{inModal ? "Create" : "Create secret link"}
|
||||
</Button>
|
||||
{inModal && (
|
||||
<ModalClose asChild>
|
||||
|
||||
@@ -12,9 +12,8 @@ import { ViewAndCopySharedSecret } from "./ViewAndCopySharedSecret";
|
||||
|
||||
const schema = yup.object({
|
||||
value: yup.string().max(10000).required().label("Shared Secret Value"),
|
||||
expiresAfterViews: yup.number().min(1).required().label("Expires After Views"),
|
||||
expiresInValue: yup.number().min(1).required().label("Expiration Value"),
|
||||
expiresInUnit: yup.string().required().label("Expiration Unit")
|
||||
expiresInValue: yup.string().required().label("Expiration Value"),
|
||||
expiresAfterViews: yup.string().required().label("Expires After Views")
|
||||
});
|
||||
|
||||
export type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
@@ -46,9 +46,8 @@ export const ShareSecretSection = () => {
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Head>
|
||||
<div className="mb-2 flex justify-between">
|
||||
<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} />}
|
||||
|
||||
@@ -1,77 +1,14 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { faTrashCan } from "@fortawesome/free-solid-svg-icons";
|
||||
import { 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 { TSharedSecret } from "@app/hooks/api/secretSharing";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const formatDate = (date: Date): string => (date ? new Date(date).toUTCString() : "");
|
||||
|
||||
const isExpired = (expiresAt: Date | number | undefined): boolean => {
|
||||
if (typeof expiresAt === "number") {
|
||||
return expiresAt <= 0;
|
||||
}
|
||||
if (expiresAt instanceof Date) {
|
||||
return new Date(expiresAt) < new Date();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const getValidityStatusText = (expiresAt: Date): string =>
|
||||
isExpired(expiresAt) ? "Expired " : "Valid for ";
|
||||
|
||||
const timeAgo = (inputDate: Date, currentDate: Date): string => {
|
||||
const now = new Date(currentDate).getTime();
|
||||
const date = new Date(inputDate).getTime();
|
||||
const elapsedMilliseconds = now - date;
|
||||
const elapsedSeconds = Math.abs(Math.floor(elapsedMilliseconds / 1000));
|
||||
const elapsedMinutes = Math.abs(Math.floor(elapsedSeconds / 60));
|
||||
const elapsedHours = Math.abs(Math.floor(elapsedMinutes / 60));
|
||||
const elapsedDays = Math.abs(Math.floor(elapsedHours / 24));
|
||||
const elapsedWeeks = Math.abs(Math.floor(elapsedDays / 7));
|
||||
const elapsedMonths = Math.abs(Math.floor(elapsedDays / 30));
|
||||
const elapsedYears = Math.abs(Math.floor(elapsedDays / 365));
|
||||
|
||||
if (elapsedYears > 0) {
|
||||
return `${elapsedYears} year${elapsedYears === 1 ? "" : "s"} ${
|
||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
||||
}`;
|
||||
}
|
||||
if (elapsedMonths > 0) {
|
||||
return `${elapsedMonths} month${elapsedMonths === 1 ? "" : "s"} ${
|
||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
||||
}`;
|
||||
}
|
||||
if (elapsedWeeks > 0) {
|
||||
return `${elapsedWeeks} week${elapsedWeeks === 1 ? "" : "s"} ${
|
||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
||||
}`;
|
||||
}
|
||||
if (elapsedDays > 0) {
|
||||
return `${elapsedDays} day${elapsedDays === 1 ? "" : "s"} ${
|
||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
||||
}`;
|
||||
}
|
||||
if (elapsedHours > 0) {
|
||||
return `${elapsedHours} hour${elapsedHours === 1 ? "" : "s"} ${
|
||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
||||
}`;
|
||||
}
|
||||
if (elapsedMinutes > 0) {
|
||||
return `${elapsedMinutes} minute${elapsedMinutes === 1 ? "" : "s"} ${
|
||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
||||
}`;
|
||||
}
|
||||
return `${elapsedSeconds} second${elapsedSeconds === 1 ? "" : "s"} ${
|
||||
elapsedMilliseconds >= 0 ? "ago" : "from now"
|
||||
}`;
|
||||
};
|
||||
|
||||
export const ShareSecretsRow = ({
|
||||
row,
|
||||
handlePopUpOpen,
|
||||
onSecretExpiration
|
||||
handlePopUpOpen
|
||||
}: {
|
||||
row: TSharedSecret;
|
||||
handlePopUpOpen: (
|
||||
@@ -84,44 +21,13 @@ export const ShareSecretsRow = ({
|
||||
id: string;
|
||||
}
|
||||
) => void;
|
||||
onSecretExpiration: (expiredSecretId: string) => void;
|
||||
}) => {
|
||||
const [currentTime, setCurrentTime] = useState(new Date());
|
||||
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => {
|
||||
setCurrentTime(new Date());
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isExpired(row.expiresAt || row.expiresAfterViews)) {
|
||||
onSecretExpiration(row.id);
|
||||
}
|
||||
}, [isExpired(row.expiresAt || row.expiresAfterViews)]);
|
||||
|
||||
return (
|
||||
<Tr key={row.id}>
|
||||
<Tr key={row.id} className="h-10">
|
||||
<Td>{`${row.encryptedValue.substring(0, 5)}...`}</Td>
|
||||
<Td>
|
||||
<p className="text-sm text-yellow-400">{timeAgo(row.createdAt, currentTime)}</p>
|
||||
<p className="text-xs text-gray-500">{formatDate(row.createdAt)}</p>
|
||||
</Td>
|
||||
<Td>
|
||||
<>
|
||||
<p className={`text-sm ${isExpired(row.expiresAt) ? "text-red-500" : "text-green-500"}`}>
|
||||
{getValidityStatusText(row.expiresAt!) + timeAgo(row.expiresAt!, currentTime)}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">{formatDate(row.expiresAt!)}</p>
|
||||
</>
|
||||
</Td>
|
||||
<Td>
|
||||
<p className={`text-sm ${row.expiresAfterViews <= 0 ? "text-red-500" : "text-green-500"}`}>
|
||||
{row.expiresAfterViews}
|
||||
</p>
|
||||
</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={() =>
|
||||
@@ -130,10 +36,10 @@ export const ShareSecretsRow = ({
|
||||
id: row.id
|
||||
})
|
||||
}
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="delete"
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrashCan} />
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
</Td>
|
||||
</Tr>
|
||||
|
||||
@@ -31,38 +31,25 @@ type Props = {
|
||||
|
||||
export const ShareSecretsTable = ({ handlePopUpOpen }: Props) => {
|
||||
const { isLoading, data = [] } = useGetSharedSecrets();
|
||||
|
||||
let tableData = data.filter(
|
||||
(secret) => new Date(secret.expiresAt) > new Date() && secret.expiresAfterViews > 0
|
||||
);
|
||||
const handleSecretExpiration = () => {
|
||||
tableData = data.filter(
|
||||
(secret) => new Date(secret.expiresAt) > new Date() && secret.expiresAfterViews > 0
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Encrypted Secret</Th> <Th>Created</Th> <Th>Valid Until</Th> <Th>Views Left</Th>
|
||||
<Th aria-label="button" />
|
||||
<Th>Encrypted Secret</Th>
|
||||
<Th>Created</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 &&
|
||||
tableData &&
|
||||
tableData.map((row) => (
|
||||
<ShareSecretsRow
|
||||
key={row.id}
|
||||
row={row}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
onSecretExpiration={handleSecretExpiration}
|
||||
/>
|
||||
data?.map((row) => (
|
||||
<ShareSecretsRow key={row.id} row={row} handlePopUpOpen={handlePopUpOpen} />
|
||||
))}
|
||||
{!isLoading && tableData && tableData?.length === 0 && (
|
||||
{!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} />
|
||||
|
||||
@@ -14,6 +14,8 @@ import { useGetActiveSharedSecretByIdAndHashedHex } from "@app/hooks/api/secretS
|
||||
import { AddShareSecretModal } from "../ShareSecretPage/components/AddShareSecretModal";
|
||||
import { SecretTable } from "./components";
|
||||
|
||||
// note: isNewSession: controls if the user is sharing a new secret or viewing a shared secret
|
||||
|
||||
export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean }) => {
|
||||
const router = useRouter();
|
||||
const { id, key: urlEncodedPublicKey } = router.query;
|
||||
@@ -55,7 +57,7 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
|
||||
return (
|
||||
<div className="flex h-screen flex-col overflow-y-auto bg-gradient-to-tr from-mineshaft-700 to-bunker-800 text-gray-200 dark:[color-scheme:dark]">
|
||||
<Head>
|
||||
<title>Secret Shared | Infisical</title>
|
||||
<title>Infisical | Secret Sharing</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex w-full flex-grow items-center justify-center dark:[color-scheme:dark]">
|
||||
@@ -94,7 +96,6 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isNewSession && (
|
||||
<div className="px-0 sm:px-6">
|
||||
<AddShareSecretModal
|
||||
|
||||
Reference in New Issue
Block a user