improvements: address feedback

This commit is contained in:
Scott Wilson
2025-04-02 14:11:59 -07:00
parent 766c1242fd
commit 577c81be65
44 changed files with 453 additions and 269 deletions
@@ -4,7 +4,7 @@ import { format, formatDistanceToNow } from "date-fns";
import { twMerge } from "tailwind-merge";
import { Tooltip } from "@app/components/v2";
import { Badge, BadgeProps } from "@app/components/v2/Badge/Badge";
import { Badge } from "@app/components/v2/Badge/Badge";
import { SecretRotationStatus, TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2";
type Props = {
@@ -78,40 +78,30 @@ export const SecretRotationV2StatusBadge = ({ secretRotation, className }: Props
const daysToRotation =
(new Date(nextRotationAt).getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24);
let variant: BadgeProps["variant"];
let label: string;
let tooltipContent: string;
if (daysToRotation >= 7) {
variant = "success";
label = `Rotates ${formatDistanceToNow(nextRotationAt, { addSuffix: true })}`;
tooltipContent = `Rotates ${format(nextRotationAt, "MM/dd/yyyy")} at ${format(nextRotationAt, "h:mm aa")}.`;
} else if (daysToRotation < 0) {
variant = "primary";
label = "Rotating";
tooltipContent = `Rotates on ${format(nextRotationAt, "MM/dd/yyyy")} at ${format(nextRotationAt, "h:mm aa")}.`;
} else if (daysToRotation < 1) {
variant = "primary";
label = `Rotates ${formatDistanceToNow(nextRotationAt, { addSuffix: true })}`;
tooltipContent = `Rotates on ${format(nextRotationAt, "MM/dd/yyyy")} at ${format(nextRotationAt, "h:mm aa")}.`;
} else {
variant = "primary";
label = `Rotates ${formatDistanceToNow(nextRotationAt, { addSuffix: true })}`;
tooltipContent = `Rotates on ${format(nextRotationAt, "MM/dd/yyyy")} at ${format(nextRotationAt, "h:mm aa")}.`;
}
return (
<Tooltip className="max-w-lg" content={tooltipContent}>
<Tooltip
className="max-w-lg"
content={
<>
<span>
Rotates on {format(nextRotationAt, "MM/dd/yyyy")} at {format(nextRotationAt, "h:mm aa")}
</span>{" "}
<span className="text-mineshaft-300">(Local Time)</span>
</>
}
>
<div>
<Badge
variant={variant}
variant={daysToRotation >= 7 ? "success" : "primary"}
className={twMerge(
"flex h-5 w-min items-center gap-1.5 whitespace-nowrap capitalize",
className
)}
>
<FontAwesomeIcon icon={faRotate} />
{label}
{daysToRotation < 0
? "Rotating"
: `Rotates ${formatDistanceToNow(nextRotationAt, { addSuffix: true })}`}
</Badge>
</div>
</Tooltip>
@@ -59,11 +59,12 @@ const Content = ({ secretRotation }: ContentProps) => {
<div className="flex flex-col gap-y-4">
{Component}
{nextRotationAt && (
<div className="flex items-center gap-x-1.5 text-sm text-mineshaft-300">
<div className="flex items-center gap-x-1.5 text-sm text-mineshaft-200">
<FontAwesomeIcon icon={faRotate} className="text-mineshaft-400" />
<span>
Next rotation occurs on: {format(nextRotationAt, "MM/dd/yyyy")} at{" "}
{format(nextRotationAt, "h:mm aa")}
{format(nextRotationAt, "h:mm aa")}{" "}
<span className="text-mineshaft-300">(Local Time)</span>
</span>
</div>
)}
@@ -14,9 +14,7 @@ type Props = {
};
export const SecretRotationV2ConfigurationFields = ({ isUpdate, environments }: Props) => {
const { control, watch } = useFormContext<TSecretRotationV2Form>();
console.log(watch("rotateAtUtc"));
const { control } = useFormContext<TSecretRotationV2Form>();
return (
<>
@@ -44,7 +44,24 @@ export const SecretRotationV2ConnectionField = ({ onChange: callback, isUpdate }
isError={Boolean(error)}
errorText={error?.message}
label={`${connectionName} Connection`}
helperText={isUpdate ? "Cannot be updated" : undefined}
helperText={
isUpdate ? (
"Cannot be updated"
) : (
<p>
Check out{" "}
<a
href={`https://infisical.com/docs/integrations/app-connections/${app}`}
target="_blank"
className="underline"
rel="noopener noreferrer"
>
our docs
</a>{" "}
to ensure your connection has the required permissions for secret rotation.
</p>
)
}
>
<FilterableSelect
value={value}
@@ -46,10 +46,17 @@ export const SqlRotationParametersFields = () => {
/>
<NoticeBannerV2 title="Example Create User Statement">
<p className="mb-3 text-sm text-mineshaft-300">
Infisical requires two database users to be created for rotation. Below is an example
statement for creating the required users. You may need to modify it to suit your needs.
Infisical requires two database users to be created for rotation.
</p>
<p className="text-sm">
<p className="mb-3 text-sm text-mineshaft-300">
These users are intended to be solely managed by Infisical. Altering their login after
rotation may cause unexpected failure.
</p>
<p className="mb-3 text-sm text-mineshaft-300">
Below is an example statement for creating the required users. You may need to modify it
to suit your needs.
</p>
<p className="mb-3 text-sm">
<pre className="whitespace-pre-wrap rounded border border-mineshaft-700 bg-mineshaft-800 p-2 text-mineshaft-300">
{rotationOption!.template.createUserStatement}
</pre>
@@ -1,15 +1,22 @@
import { ReactNode } from "react";
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
type Props = {
title: string;
children: ReactNode;
className?: string;
};
export const NoticeBannerV2 = ({ title, children }: Props) => {
export const NoticeBannerV2 = ({ title, children, className }: Props) => {
return (
<div className="flex flex-col rounded-r border-l-2 border-l-primary bg-mineshaft-300/5 px-4 py-2.5">
<div
className={twMerge(
"flex flex-col rounded-r border-l-2 border-l-primary bg-mineshaft-300/5 px-4 py-2.5",
className
)}
>
<div className="mb-1 flex items-center text-sm">
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1.5 text-primary" />
{title}
+7 -11
View File
@@ -14,15 +14,11 @@ export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnectio
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql
};
export const getRotateAtLocal = ({ hours, minutes }: TSecretRotationV2["rotateAtUtc"]) =>
new Date(
Date.UTC(
new Date().getUTCFullYear(),
new Date().getUTCMonth(),
new Date().getUTCDate(),
hours,
minutes,
0,
0
)
export const getRotateAtLocal = ({ hours, minutes }: TSecretRotationV2["rotateAtUtc"]) => {
const now = new Date();
// convert utc rotation time to local datetime
return new Date(
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), hours, minutes, 0, 0)
);
};
@@ -32,6 +32,7 @@ export type TSecretApprovalSecChange = {
secretKey: string;
secretValue?: string;
secretComment?: string;
isRotatedSecret?: boolean;
tags?: string[];
};
@@ -2,6 +2,7 @@
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { dashboardKeys } from "@app/hooks/api/dashboard/queries";
import { SecretType, SecretV3RawSanitized } from "../secrets/types";
import {
@@ -82,7 +83,8 @@ export const useGetSnapshotSecrets = ({ snapshotId }: TSnapshotDataProps) =>
createdAt: secretVersion.createdAt,
updatedAt: secretVersion.updatedAt,
type: "modified",
version: secretVersion.version
version: secretVersion.version,
isRotatedSecret: secretVersion.isRotatedSecret
};
if (secretVersion.type === SecretType.Personal) {
@@ -162,6 +164,12 @@ export const usePerformSecretRollback = () => {
queryClient.invalidateQueries({
queryKey: secretSnapshotKeys.count({ workspaceId, environment, directory })
});
queryClient.invalidateQueries({
queryKey: dashboardKeys.getDashboardSecrets({
projectId: workspaceId,
secretPath: directory ?? "/"
})
});
}
});
};
@@ -11,7 +11,7 @@ export type TSecretSnapshot = {
export type TSnapshotData = Omit<TSecretSnapshot, "secretVersions"> & {
id: string;
secretVersions: SecretVersions[];
secretVersions: (SecretVersions & { isRotatedSecret?: boolean })[];
folderVersion: Array<{ name: string; id: string }>;
environment: WorkspaceEnv;
};
@@ -1,5 +1,6 @@
import { createNotification } from "@app/components/notifications";
import { DeleteActionModal } from "@app/components/v2";
import { NoticeBannerV2 } from "@app/components/v2/NoticeBannerV2/NoticeBannerV2";
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
import { TAppConnection, useDeleteAppConnection } from "@app/hooks/api/appConnections";
@@ -46,6 +47,17 @@ export const DeleteAppConnectionModal = ({ isOpen, onOpenChange, appConnection }
title={`Are you sure want to delete ${name}?`}
deleteKey={name}
onDeleteApproved={handleDeleteAppConnection}
/>
>
{appConnection.isPlatformManagedCredentials && (
<NoticeBannerV2 className="mt-3" title="Platform Managed Credentials">
<p className="text-sm text-bunker-300">
This App Connection&#39;s credentials are managed by Infisical.
</p>
<p className="mt-3 text-sm text-bunker-300">
By deleting this connection you may lose permanent access to the associated resource.
</p>
</NoticeBannerV2>
)}
</DeleteActionModal>
);
};
@@ -97,7 +97,13 @@ export const SecretApprovalRequestChangeItem = ({
<Td className="text-red-600">OLD</Td>
<Td>{secretVersion?.secretKey}</Td>
<Td>
<SecretInput isReadOnly value={secretVersion?.secretValue} />
{newVersion?.isRotatedSecret ? (
<span className="text-mineshaft-400">
Rotated Secret value will not be affected
</span>
) : (
<SecretInput isReadOnly value={secretVersion?.secretValue} />
)}
</Td>
<Td>{secretVersion?.secretComment}</Td>
<Td className="flex flex-wrap gap-2">
@@ -146,7 +152,13 @@ export const SecretApprovalRequestChangeItem = ({
<Td className="text-green-600">NEW</Td>
<Td>{newVersion?.secretKey}</Td>
<Td>
<SecretInput isReadOnly value={newVersion?.secretValue} />
{newVersion?.isRotatedSecret ? (
<span className="text-mineshaft-400">
Rotated Secret value will not be affected
</span>
) : (
<SecretInput isReadOnly value={newVersion?.secretValue} />
)}
</Td>
<Td>{newVersion?.secretComment}</Td>
<Td className="flex flex-wrap gap-2">
@@ -76,7 +76,7 @@ export const SecretItem = ({ mode, preSecret, postSecret }: Props) => {
<FontAwesomeIcon icon={faKey} />
</div>
<div className="flex flex-grow items-center space-x-4 px-4 py-3">
{mode === "modified" ? (
{mode === "modified" && !preSecret?.isRotatedSecret ? (
<>
<div>{preSecret?.key}</div>
<div className="rounded-lg bg-primary px-1 py-0.5 text-xs font-bold text-black">
@@ -90,7 +90,14 @@ export const SecretItem = ({ mode, preSecret, postSecret }: Props) => {
</div>
</>
) : (
postSecret.key
<>
{postSecret.key}
{postSecret.isRotatedSecret && (
<span className="ml-2 text-mineshaft-400">
Rotated Secrets are not affected by Rollback
</span>
)}
</>
)}
</div>
</div>
@@ -33,11 +33,13 @@ type Props = {
const LOADER_TEXT = ["Fetching your snapshot", "Creating the difference view"];
const deepCompareSecrets = (lhs: SecretV3RawSanitized, rhs: SecretV3RawSanitized) =>
lhs.key === rhs.key &&
lhs.value === rhs.value &&
lhs.comment === rhs.comment &&
lhs?.valueOverride === rhs?.valueOverride &&
JSON.stringify(lhs.tags) === JSON.stringify(rhs.tags);
lhs.isRotatedSecret ||
rhs.isRotatedSecret ||
(lhs.key === rhs.key &&
lhs.value === rhs.value &&
lhs.comment === rhs.comment &&
lhs?.valueOverride === rhs?.valueOverride &&
JSON.stringify(lhs.tags) === JSON.stringify(rhs.tags));
export const SnapshotView = ({
snapshotId,