mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 00:52:09 +00:00
improvements: address feedback
This commit is contained in:
@@ -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>
|
||||
|
||||
+3
-2
@@ -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>
|
||||
)}
|
||||
|
||||
+1
-3
@@ -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 (
|
||||
<>
|
||||
|
||||
+18
-1
@@ -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}
|
||||
|
||||
+10
-3
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
+13
-1
@@ -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'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>
|
||||
);
|
||||
};
|
||||
|
||||
+14
-2
@@ -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">
|
||||
|
||||
+9
-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>
|
||||
|
||||
+7
-5
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user