Merge origin/main to feat/pki-sync-v2

This commit is contained in:
Carlos Monastyrski
2025-10-29 13:24:37 -03:00
276 changed files with 2996 additions and 2313 deletions
@@ -1,9 +1,11 @@
import { components, OptionProps } from "react-select";
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons";
import { faBuilding, faPlus } from "@fortawesome/free-solid-svg-icons";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Badge, Tooltip } from "@app/components/v2";
import { Tooltip } from "@app/components/v2";
import { Badge, OrgIcon, SubOrgIcon } from "@app/components/v3";
import { useOrganization } from "@app/context";
import { TAvailableAppConnection } from "@app/hooks/api/appConnections";
export const AppConnectionOption = ({
@@ -13,6 +15,8 @@ export const AppConnectionOption = ({
}: OptionProps<TAvailableAppConnection>) => {
const isCreateOption = props.data.id === "_create";
const { isSubOrganization } = useOrganization();
return (
<components.Option isSelected={isSelected} {...props}>
<div className="flex flex-row items-center justify-between">
@@ -23,15 +27,22 @@ export const AppConnectionOption = ({
</div>
) : (
<>
<p className="truncate">{children}</p>
<p className="mr-auto truncate">{children}</p>
{!props.data.projectId && (
<Tooltip content="This connection belongs to your organization.">
<div className="mr-auto ml-2">
<Badge className="flex h-5 w-min items-center gap-1 bg-mineshaft-400/50 whitespace-nowrap text-bunker-300 hover:text-bunker-300">
<FontAwesomeIcon icon={faBuilding} size="sm" />
<Tooltip
content={`This connection belongs to your ${isSubOrganization ? "sub-" : ""}organization.`}
>
{isSubOrganization ? (
<Badge variant="sub-org">
<SubOrgIcon />
Sub-Organization
</Badge>
) : (
<Badge variant="org">
<OrgIcon />
Organization
</Badge>
</div>
)}
</Tooltip>
)}
{isSelected && (
@@ -4,7 +4,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import {
Badge,
Button,
Checkbox,
EmptyState,
@@ -20,6 +19,7 @@ import {
THead,
Tr
} from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { useProject } from "@app/context";
import {
CertStatus,
@@ -1,27 +1,20 @@
import { ReactNode, useEffect, useMemo, useState } from "react";
import {
faCheck,
faDownload,
faTriangleExclamation,
faXmark,
IconDefinition
} from "@fortawesome/free-solid-svg-icons";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { differenceInSeconds } from "date-fns";
import { twMerge } from "tailwind-merge";
import { CheckIcon, DownloadIcon, LucideIcon, TriangleAlertIcon } from "lucide-react";
import { Badge, Tooltip } from "@app/components/v2";
import { BadgeProps } from "@app/components/v2/Badge/Badge";
import { Tooltip } from "@app/components/v2";
import { Badge, TBadgeProps } from "@app/components/v3";
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
import { PkiSyncStatus, TPkiSync } from "@app/hooks/api/pkiSyncs";
type Props = {
pkiSync: TPkiSync;
className?: string;
mini?: boolean;
};
export const PkiSyncImportStatusBadge = ({ pkiSync, className, mini }: Props) => {
export const PkiSyncImportStatusBadge = ({ pkiSync, mini }: Props) => {
const { importStatus, lastImportMessage, lastImportedAt, destination } = pkiSync;
const [hide, setHide] = useState(importStatus === PkiSyncStatus.Succeeded);
const destinationName = PKI_SYNC_MAP[destination].name;
@@ -50,24 +43,24 @@ export const PkiSyncImportStatusBadge = ({ pkiSync, className, mini }: Props) =>
if (!importStatus || hide) return null;
let variant: BadgeProps["variant"];
let variant: TBadgeProps["variant"];
let label: string;
let icon: IconDefinition;
let Icon: LucideIcon;
let tooltipContent: ReactNode;
switch (importStatus) {
case PkiSyncStatus.Pending:
case PkiSyncStatus.Running:
variant = "primary";
variant = "warning";
label = "Importing Certificates...";
tooltipContent = `Importing certificates from ${destinationName}. This may take a moment.`;
icon = faDownload;
Icon = DownloadIcon;
break;
case PkiSyncStatus.Failed:
variant = "danger";
label = "Failed to Import Certificates";
icon = faTriangleExclamation;
Icon = TriangleAlertIcon;
tooltipContent = (
<div className="flex flex-col gap-2 py-1 whitespace-normal">
{failureMessage && (
@@ -93,20 +86,15 @@ export const PkiSyncImportStatusBadge = ({ pkiSync, className, mini }: Props) =>
tooltipContent = "Successfully imported certificates.";
variant = "success";
label = "Certificates Imported";
icon = faCheck;
Icon = CheckIcon;
}
return (
<Tooltip position="bottom" className="max-w-sm" content={tooltipContent}>
<div>
<Badge
className={twMerge("flex h-5 w-min items-center gap-1.5 whitespace-nowrap", className)}
variant={variant}
>
<FontAwesomeIcon icon={icon} />
{!mini && <span>{label}</span>}
</Badge>
</div>
<Badge isSquare={mini} variant={variant}>
<Icon />
{!mini && label}
</Badge>
</Tooltip>
);
};
@@ -1,6 +1,4 @@
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { DocumentationLinkBadge } from "@app/components/v3";
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
import { PkiSync } from "@app/hooks/api/pkiSyncs";
@@ -20,23 +18,11 @@ export const PkiSyncModalHeader = ({ destination, isConfigured }: Props) => {
className="h-12 w-12 rounded-md bg-bunker-500 p-2"
/>
<div>
<div className="flex items-center text-mineshaft-300">
<div className="flex items-center gap-x-2 text-mineshaft-300">
{destinationDetails.name} Certificate Sync
<a
target="_blank"
href={`https://infisical.com/docs/integrations/pki-syncs/${destination}`}
className="mb-1 ml-1"
rel="noopener noreferrer"
>
<div className="inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1 mb-[0.03rem] text-[12px]" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1 text-[10px]"
/>
</div>
</a>
<DocumentationLinkBadge
href={`https://infisical.com/docs/documentation/platform/pki/certificate-syncs/${destination}`}
/>
</div>
<p className="text-sm leading-4 text-mineshaft-400">
{isConfigured
@@ -1,27 +1,20 @@
import { ReactNode, useEffect, useMemo, useState } from "react";
import {
faCheck,
faEraser,
faTriangleExclamation,
faXmark,
IconDefinition
} from "@fortawesome/free-solid-svg-icons";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { differenceInSeconds } from "date-fns";
import { twMerge } from "tailwind-merge";
import { AlertTriangleIcon, CheckIcon, EraserIcon, LucideIcon } from "lucide-react";
import { Badge, Tooltip } from "@app/components/v2";
import { BadgeProps } from "@app/components/v2/Badge/Badge";
import { Tooltip } from "@app/components/v2";
import { Badge, TBadgeProps } from "@app/components/v3";
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
import { PkiSyncStatus, TPkiSync } from "@app/hooks/api/pkiSyncs";
type Props = {
pkiSync: TPkiSync;
className?: string;
mini?: boolean;
};
export const PkiSyncRemoveStatusBadge = ({ pkiSync, className, mini }: Props) => {
export const PkiSyncRemoveStatusBadge = ({ pkiSync, mini }: Props) => {
const { removeStatus, lastRemoveMessage, lastRemovedAt, destination } = pkiSync;
const [hide, setHide] = useState(removeStatus === PkiSyncStatus.Succeeded);
const destinationName = PKI_SYNC_MAP[destination].name;
@@ -50,24 +43,24 @@ export const PkiSyncRemoveStatusBadge = ({ pkiSync, className, mini }: Props) =>
if (!removeStatus || hide) return null;
let variant: BadgeProps["variant"];
let variant: TBadgeProps["variant"];
let label: string;
let icon: IconDefinition;
let Icon: LucideIcon;
let tooltipContent: ReactNode;
switch (removeStatus) {
case PkiSyncStatus.Pending:
case PkiSyncStatus.Running:
variant = "primary";
variant = "warning";
label = "Removing Certificates...";
tooltipContent = `Removing certificates from ${destinationName}. This may take a moment.`;
icon = faEraser;
Icon = EraserIcon;
break;
case PkiSyncStatus.Failed:
variant = "danger";
label = "Failed to Remove Certificates";
icon = faTriangleExclamation;
Icon = AlertTriangleIcon;
tooltipContent = (
<div className="flex flex-col gap-2 py-1 whitespace-normal">
{failureMessage && (
@@ -93,20 +86,15 @@ export const PkiSyncRemoveStatusBadge = ({ pkiSync, className, mini }: Props) =>
tooltipContent = "Successfully removed certificates.";
variant = "success";
label = "Certificates Removed";
icon = faCheck;
Icon = CheckIcon;
}
return (
<Tooltip position="bottom" className="max-w-sm" content={tooltipContent}>
<div>
<Badge
className={twMerge("flex h-5 w-min items-center gap-1.5 whitespace-nowrap", className)}
variant={variant}
>
<FontAwesomeIcon icon={icon} />
{!mini && <span>{label}</span>}
</Badge>
</div>
<Badge isSquare={mini} variant={variant}>
<Icon />
{!mini && label}
</Badge>
</Tooltip>
);
};
@@ -69,7 +69,10 @@ export const PkiSyncSelect = ({ onSelect }: Props) => {
type="button"
onClick={() =>
enterprise && !subscription.enterpriseCertificateSyncs
? handlePopUpOpen("upgradePlan")
? handlePopUpOpen("upgradePlan", {
isEnterpriseFeature: true,
text: "You can use every Certificate Sync if you switch to Infisical's Enterprise plan."
})
: onSelect(destination)
}
className="group relative flex h-28 cursor-pointer flex-col items-center justify-center overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-700 p-4 duration-200 hover:bg-mineshaft-600"
@@ -148,6 +151,7 @@ export const PkiSyncSelect = ({ onSelect }: Props) => {
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature}
text="You can use every Certificate Sync if you switch to Infisical's Enterprise plan."
/>
</div>
@@ -1,55 +1,51 @@
import {
faCheck,
faExclamationTriangle,
faHourglass,
faRotate,
IconDefinition
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
AlertTriangleIcon,
CheckIcon,
HourglassIcon,
LucideIcon,
RefreshCwIcon
} from "lucide-react";
import { Badge, BadgeProps } from "@app/components/v2/Badge/Badge";
import { Badge, TBadgeProps } from "@app/components/v3";
import { PkiSyncStatus } from "@app/hooks/api/pkiSyncs";
type Props = {
status: PkiSyncStatus;
} & Omit<BadgeProps, "children" | "variant">;
} & Omit<TBadgeProps, "children" | "variant">;
export const PkiSyncStatusBadge = ({ status }: Props) => {
let variant: BadgeProps["variant"];
let variant: TBadgeProps["variant"];
let text: string;
let icon: IconDefinition;
let Icon: LucideIcon;
switch (status) {
case PkiSyncStatus.Failed:
variant = "danger";
text = "Failed to Sync";
icon = faExclamationTriangle;
Icon = AlertTriangleIcon;
break;
case PkiSyncStatus.Succeeded:
variant = "success";
text = "Synced";
icon = faCheck;
Icon = CheckIcon;
break;
case PkiSyncStatus.Pending:
variant = "primary";
variant = "info";
text = "Queued";
icon = faHourglass;
Icon = HourglassIcon;
break;
case PkiSyncStatus.Running:
default:
variant = "primary";
variant = "info";
text = "Syncing";
icon = faRotate;
Icon = RefreshCwIcon;
break;
}
return (
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap" variant={variant}>
<FontAwesomeIcon
icon={icon}
className={status === PkiSyncStatus.Running ? "animate-spin" : ""}
/>
<span>{text}</span>
<Badge variant={variant}>
<Icon className={status === PkiSyncStatus.Running ? "animate-spin" : ""} />
{text}
</Badge>
);
};
@@ -2,7 +2,6 @@ import { faPlug, faRefresh, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Badge,
EmptyState,
IconButton,
Table,
@@ -13,6 +12,7 @@ import {
THead,
Tr
} from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { TPkiSync } from "@app/hooks/api/pkiSyncs";
type Props = {
@@ -29,10 +29,10 @@ const getSyncStatusBadge = (status?: string) => {
case "FAILED":
return <Badge variant="danger">Failed</Badge>;
case "RUNNING":
return <Badge variant="primary">Running</Badge>;
return <Badge variant="warning">Running</Badge>;
case "PENDING":
default:
return <Badge variant="primary">Pending</Badge>;
return <Badge variant="warning">Pending</Badge>;
}
};
@@ -71,7 +71,7 @@ export const PkiSyncTable = ({ pkiSyncs, onEdit, onDelete, onTrigger }: Props) =
<Td>{pkiSync.name}</Td>
<Td>
<div className="flex items-center space-x-2">
<Badge variant="primary">{pkiSync.destination}</Badge>
<Badge variant="warning">{pkiSync.destination}</Badge>
</div>
</Td>
<Td>{pkiSync.appConnectionName || "Unknown"}</Td>
@@ -1,6 +1,7 @@
import { useFormContext } from "react-hook-form";
import { Badge, GenericFieldLabel } from "@app/components/v2";
import { GenericFieldLabel } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { useProject } from "@app/context";
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
import { useListWorkspaceCertificates } from "@app/hooks/api/projects";
@@ -1,6 +1,4 @@
import { useEffect, useState } from "react";
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useRouterState } from "@tanstack/react-router";
import { SecretRotationV2Form } from "@app/components/secret-rotations-v2/forms";
@@ -8,6 +6,7 @@ import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms
import { SecretRotationV2ModalHeader } from "@app/components/secret-rotations-v2/SecretRotationV2ModalHeader";
import { SecretRotationV2Select } from "@app/components/secret-rotations-v2/SecretRotationV2Select";
import { Modal, ModalContent } from "@app/components/v2";
import { DocumentationLinkBadge } from "@app/components/v3";
import { ProjectEnv } from "@app/hooks/api/projects/types";
import { SecretRotation, TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2";
@@ -115,23 +114,9 @@ export const CreateSecretRotationV2Modal = ({ onOpenChange, isOpen, ...props }:
selectedRotation ? (
<SecretRotationV2ModalHeader isConfigured={false} type={selectedRotation} />
) : (
<div className="flex items-center text-mineshaft-300">
<div className="flex items-center gap-x-2 text-mineshaft-300">
Add Secret Rotation
<a
target="_blank"
href="https://infisical.com/docs/documentation/platform/secret-rotation/overview"
className="mb-1 ml-1"
rel="noopener noreferrer"
>
<div className="inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1 mb-[0.03rem] text-[12px]" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1 text-[10px]"
/>
</div>
</a>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/secret-rotation/overview" />
</div>
)
}
@@ -1,6 +1,4 @@
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { DocumentationLinkBadge } from "@app/components/v3";
import { SECRET_ROTATION_MAP } from "@app/helpers/secretRotationsV2";
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
@@ -20,23 +18,11 @@ export const SecretRotationV2ModalHeader = ({ type, isConfigured }: Props) => {
className="h-12 w-12 rounded-md bg-bunker-500 p-2"
/>
<div>
<div className="flex items-center text-mineshaft-300">
<div className="flex items-center gap-x-2 text-mineshaft-300">
{destinationDetails.name} Rotation
<a
target="_blank"
<DocumentationLinkBadge
href={`https://infisical.com/docs/documentation/platform/secret-rotation/${type}`}
className="mb-1 ml-1"
rel="noopener noreferrer"
>
<div className="inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1 mb-[0.03rem] text-[12px]" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1 text-[10px]"
/>
</div>
</a>
/>
</div>
<p className="text-sm leading-4 text-mineshaft-400">
{isConfigured
@@ -1,18 +1,17 @@
import { faBan, faRotate, faXmark } from "@fortawesome/free-solid-svg-icons";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format, formatDistanceToNow } from "date-fns";
import { twMerge } from "tailwind-merge";
import { BanIcon, RefreshCwIcon, XIcon } from "lucide-react";
import { Tooltip } from "@app/components/v2";
import { Badge } from "@app/components/v2/Badge/Badge";
import { Badge } from "@app/components/v3";
import { SecretRotationStatus, TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2";
type Props = {
secretRotation: TSecretRotationV2;
className?: string;
};
export const SecretRotationV2StatusBadge = ({ secretRotation, className }: Props) => {
export const SecretRotationV2StatusBadge = ({ secretRotation }: Props) => {
const { isAutoRotationEnabled, rotationStatus, nextRotationAt, lastRotationMessage } =
secretRotation;
@@ -50,28 +49,18 @@ export const SecretRotationV2StatusBadge = ({ secretRotation, className }: Props
</div>
}
>
<div>
<Badge
variant="danger"
className={twMerge("flex h-5 w-min items-center gap-1.5 whitespace-nowrap", className)}
>
<FontAwesomeIcon icon={faXmark} />
Rotation Failed
</Badge>
</div>
<Badge variant="danger">
<XIcon />
Rotation Failed
</Badge>
</Tooltip>
);
}
if (!isAutoRotationEnabled) {
return (
<Badge
className={twMerge(
"flex h-5 w-min items-center gap-1.5 bg-mineshaft-400/50 whitespace-nowrap text-bunker-300",
className
)}
>
<FontAwesomeIcon icon={faBan} />
<Badge variant="neutral">
<BanIcon />
Auto-Rotation Disabled
</Badge>
);
@@ -92,20 +81,12 @@ export const SecretRotationV2StatusBadge = ({ secretRotation, className }: Props
</>
}
>
<div>
<Badge
variant={daysToRotation >= 7 ? "success" : "primary"}
className={twMerge(
"flex h-5 w-min items-center gap-1.5 whitespace-nowrap capitalize",
className
)}
>
<FontAwesomeIcon icon={faRotate} />
{daysToRotation < 0
? "Rotating"
: `Rotates ${formatDistanceToNow(nextRotationAt, { addSuffix: true })}`}
</Badge>
</div>
<Badge variant={daysToRotation >= 7 ? "info" : "warning"} className="capitalize">
<RefreshCwIcon />
{daysToRotation < 0
? "Rotating"
: `Rotates ${formatDistanceToNow(nextRotationAt, { addSuffix: true })}`}
</Badge>
</Tooltip>
);
};
@@ -1,8 +1,10 @@
import { ReactNode } from "react";
import { faArrowRight, faKey } from "@fortawesome/free-solid-svg-icons";
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { KeyIcon } from "lucide-react";
import { Badge, FormLabel } from "@app/components/v2";
import { FormLabel } from "@app/components/v2";
import { Badge } from "@app/components/v3";
type Props = {
items: { name: string; input: ReactNode }[];
@@ -32,9 +34,10 @@ export const SecretsMappingTable = ({ items }: Props) => {
<tr key={name}>
<td className="whitespace-nowrap">
<div className="mb-4 flex h-full items-start justify-center">
<Badge className="pointer-events-none flex h-[36px] w-full items-center justify-center gap-1.5 border border-mineshaft-600 bg-mineshaft-600 whitespace-nowrap text-bunker-200">
<FontAwesomeIcon icon={faKey} />
<span>{name}</span>
{/* TODO(scott): probably shouldn't be a badge */}
<Badge variant="neutral" className="h-[36px] w-full justify-center text-xs">
<KeyIcon />
{name}
</Badge>
</div>
</td>
@@ -1,10 +1,9 @@
import { useEffect, useState } from "react";
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useRouterState } from "@tanstack/react-router";
import { TSecretScanningDataSourceForm } from "@app/components/secret-scanning/forms/schemas";
import { Modal, ModalContent } from "@app/components/v2";
import { DocumentationLinkBadge } from "@app/components/v3";
import {
SecretScanningDataSource,
TSecretScanningDataSource
@@ -108,23 +107,9 @@ export const CreateSecretScanningDataSourceModal = ({ onOpenChange, isOpen, ...p
selectedDataSource ? (
<SecretScanningDataSourceModalHeader isConfigured={false} type={selectedDataSource} />
) : (
<div className="flex items-center text-mineshaft-300">
<div className="flex items-center gap-x-2 text-mineshaft-300">
Add Data Source
<a
target="_blank"
href="https://infisical.com/docs/documentation/platform/secret-scanning/overview"
className="mb-1 ml-1"
rel="noopener noreferrer"
>
<div className="inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1 mb-[0.03rem] text-[12px]" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1 text-[10px]"
/>
</div>
</a>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/secret-scanning/overview" />
</div>
)
}
@@ -1,6 +1,4 @@
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { DocumentationLinkBadge } from "@app/components/v3";
import { SECRET_SCANNING_DATA_SOURCE_MAP } from "@app/helpers/secretScanningV2";
import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2";
@@ -20,23 +18,11 @@ export const SecretScanningDataSourceModalHeader = ({ type, isConfigured }: Prop
className="h-12 rounded-md bg-bunker-500 p-2"
/>
<div>
<div className="flex items-center text-mineshaft-300">
<div className="flex items-center gap-x-2 text-mineshaft-300">
{dataSourceDetails.name} Data Source
<a
target="_blank"
<DocumentationLinkBadge
href={`https://infisical.com/docs/documentation/platform/secret-scanning/${type}`}
className="mb-1 ml-1"
rel="noopener noreferrer"
>
<div className="inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1 mb-[0.03rem] text-[12px]" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1 text-[10px]"
/>
</div>
</a>
/>
</div>
<p className="text-sm leading-4 text-mineshaft-400">
{isConfigured ? "Edit" : "Connect a"} {dataSourceDetails.name} Data Source
@@ -1,22 +1,23 @@
import { faArrowRotateForward, faCheck, faXmark } from "@fortawesome/free-solid-svg-icons";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { formatDistance } from "date-fns";
import { twMerge } from "tailwind-merge";
import { CheckIcon, RotateCwIcon, XIcon } from "lucide-react";
import { Badge, Tooltip } from "@app/components/v2";
import { Tooltip } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { SecretScanningScanStatus } from "@app/hooks/api/secretScanningV2";
type Props = {
status: SecretScanningScanStatus;
statusMessage?: string | null;
className?: string;
scannedAt?: string | null;
};
export const SecretScanningScanStatusBadge = ({
status,
statusMessage,
className,
scannedAt
}: Props) => {
if (status === SecretScanningScanStatus.Failed) {
@@ -53,11 +54,8 @@ export const SecretScanningScanStatusBadge = ({
}
>
<div>
<Badge
variant="danger"
className={twMerge("flex h-5 w-min items-center gap-1.5 whitespace-nowrap", className)}
>
<FontAwesomeIcon icon={faXmark} />
<Badge variant="danger">
<XIcon />
Scan Error
</Badge>
</div>
@@ -67,26 +65,17 @@ export const SecretScanningScanStatusBadge = ({
if (status === SecretScanningScanStatus.Queued || status === SecretScanningScanStatus.Scanning) {
return (
<Badge
className={twMerge("flex h-5 w-min items-center gap-1.5 whitespace-nowrap", className)}
variant="primary"
>
<FontAwesomeIcon icon={faArrowRotateForward} className="animate-spin" />
<span>Scanning</span>
<Badge variant="info">
<RotateCwIcon className="animate-spin" />
Scanning
</Badge>
);
}
return (
<Badge
variant="success"
className={twMerge(
"flex h-5 w-min items-center gap-1.5 whitespace-nowrap capitalize",
className
)}
>
<FontAwesomeIcon icon={faCheck} />
<span>Complete</span>
<Badge variant="success">
<CheckIcon />
Complete
</Badge>
);
};
@@ -1,27 +1,20 @@
import { ReactNode, useEffect, useMemo, useState } from "react";
import {
faCheck,
faDownload,
faTriangleExclamation,
faXmark,
IconDefinition
} from "@fortawesome/free-solid-svg-icons";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { differenceInSeconds } from "date-fns";
import { twMerge } from "tailwind-merge";
import { AlertTriangleIcon, CheckIcon, DownloadIcon, LucideIcon } from "lucide-react";
import { Badge, Tooltip } from "@app/components/v2";
import { BadgeProps } from "@app/components/v2/Badge/Badge";
import { Tooltip } from "@app/components/v2";
import { Badge, TBadgeProps } from "@app/components/v3";
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { SecretSyncStatus, TSecretSync } from "@app/hooks/api/secretSyncs";
type Props = {
secretSync: TSecretSync;
className?: string;
mini?: boolean;
};
export const SecretSyncImportStatusBadge = ({ secretSync, className, mini }: Props) => {
export const SecretSyncImportStatusBadge = ({ secretSync, mini }: Props) => {
const { importStatus, lastImportMessage, lastImportedAt, destination } = secretSync;
const [hide, setHide] = useState(importStatus === SecretSyncStatus.Succeeded);
const destinationName = SECRET_SYNC_MAP[destination].name;
@@ -50,24 +43,24 @@ export const SecretSyncImportStatusBadge = ({ secretSync, className, mini }: Pro
if (!importStatus || hide) return null;
let variant: BadgeProps["variant"];
let variant: TBadgeProps["variant"];
let label: string;
let icon: IconDefinition;
let Icon: LucideIcon;
let tooltipContent: ReactNode;
switch (importStatus) {
case SecretSyncStatus.Pending:
case SecretSyncStatus.Running:
variant = "primary";
variant = "warning";
label = "Importing Secrets...";
tooltipContent = `Importing secrets from ${destinationName}. This may take a moment.`;
icon = faDownload;
Icon = DownloadIcon;
break;
case SecretSyncStatus.Failed:
variant = "danger";
label = "Failed to Import Secrets";
icon = faTriangleExclamation;
Icon = AlertTriangleIcon;
tooltipContent = (
<div className="flex flex-col gap-2 py-1 whitespace-normal">
{failureMessage && (
@@ -93,20 +86,15 @@ export const SecretSyncImportStatusBadge = ({ secretSync, className, mini }: Pro
tooltipContent = "Successfully imported secrets.";
variant = "success";
label = "Secrets Imported";
icon = faCheck;
Icon = CheckIcon;
}
return (
<Tooltip position="bottom" className="max-w-sm" content={tooltipContent}>
<div>
<Badge
className={twMerge("flex h-5 w-min items-center gap-1.5 whitespace-nowrap", className)}
variant={variant}
>
<FontAwesomeIcon icon={icon} />
{!mini && <span>{label}</span>}
</Badge>
</div>
<Badge isSquare={mini} variant={variant}>
<Icon />
{!mini && label}
</Badge>
</Tooltip>
);
};
@@ -1,6 +1,4 @@
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { DocumentationLinkBadge } from "@app/components/v3";
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { SecretSync } from "@app/hooks/api/secretSyncs";
@@ -20,23 +18,11 @@ export const SecretSyncModalHeader = ({ destination, isConfigured }: Props) => {
className="h-12 w-12 rounded-md bg-bunker-500 object-contain p-2"
/>
<div>
<div className="flex items-center text-mineshaft-300">
<div className="flex items-center gap-x-2 text-mineshaft-300">
{destinationDetails.name} Sync
<a
target="_blank"
<DocumentationLinkBadge
href={`https://infisical.com/docs/integrations/secret-syncs/${destination}`}
className="mb-1 ml-1"
rel="noopener noreferrer"
>
<div className="inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1 mb-[0.03rem] text-[12px]" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1 text-[10px]"
/>
</div>
</a>
/>
</div>
<p className="text-sm leading-4 text-mineshaft-400">
{isConfigured
@@ -1,27 +1,20 @@
import { ReactNode, useEffect, useMemo, useState } from "react";
import {
faCheck,
faEraser,
faTriangleExclamation,
faXmark,
IconDefinition
} from "@fortawesome/free-solid-svg-icons";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { differenceInSeconds } from "date-fns";
import { twMerge } from "tailwind-merge";
import { AlertTriangleIcon, CheckIcon, EraserIcon, LucideIcon } from "lucide-react";
import { Badge, Tooltip } from "@app/components/v2";
import { BadgeProps } from "@app/components/v2/Badge/Badge";
import { Tooltip } from "@app/components/v2";
import { Badge, TBadgeProps } from "@app/components/v3";
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { SecretSyncStatus, TSecretSync } from "@app/hooks/api/secretSyncs";
type Props = {
secretSync: TSecretSync;
className?: string;
mini?: boolean;
};
export const SecretSyncRemoveStatusBadge = ({ secretSync, className, mini }: Props) => {
export const SecretSyncRemoveStatusBadge = ({ secretSync, mini }: Props) => {
const { removeStatus, lastRemoveMessage, lastRemovedAt, destination } = secretSync;
const [hide, setHide] = useState(removeStatus === SecretSyncStatus.Succeeded);
const destinationName = SECRET_SYNC_MAP[destination].name;
@@ -50,24 +43,24 @@ export const SecretSyncRemoveStatusBadge = ({ secretSync, className, mini }: Pro
if (!removeStatus || hide) return null;
let variant: BadgeProps["variant"];
let variant: TBadgeProps["variant"];
let label: string;
let icon: IconDefinition;
let Icon: LucideIcon;
let tooltipContent: ReactNode;
switch (removeStatus) {
case SecretSyncStatus.Pending:
case SecretSyncStatus.Running:
variant = "primary";
variant = "warning";
label = "Removing Secrets...";
tooltipContent = `Removing secrets from ${destinationName}. This may take a moment.`;
icon = faEraser;
Icon = EraserIcon;
break;
case SecretSyncStatus.Failed:
variant = "danger";
label = "Failed to Remove Secrets";
icon = faTriangleExclamation;
Icon = AlertTriangleIcon;
tooltipContent = (
<div className="flex flex-col gap-2 py-1 whitespace-normal">
{failureMessage && (
@@ -93,20 +86,15 @@ export const SecretSyncRemoveStatusBadge = ({ secretSync, className, mini }: Pro
tooltipContent = "Successfully removed secrets.";
variant = "success";
label = "Secrets Removed";
icon = faCheck;
Icon = CheckIcon;
}
return (
<Tooltip position="bottom" className="max-w-sm" content={tooltipContent}>
<div>
<Badge
className={twMerge("flex h-5 w-min items-center gap-1.5 whitespace-nowrap", className)}
variant={variant}
>
<FontAwesomeIcon icon={icon} />
{!mini && <span>{label}</span>}
</Badge>
</div>
<Badge isSquare={mini} variant={variant}>
<Icon />
{!mini && label}
</Badge>
</Tooltip>
);
};
@@ -67,7 +67,9 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
type="button"
onClick={() =>
enterprise && !subscription.enterpriseSecretSyncs
? handlePopUpOpen("upgradePlan")
? handlePopUpOpen("upgradePlan", {
isEnterpriseFeature: true
})
: onSelect(destination)
}
className="group relative flex h-28 cursor-pointer flex-col items-center justify-center overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-700 p-4 duration-200 hover:bg-mineshaft-600"
@@ -145,6 +147,7 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
)}
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can use every Secret Sync if you switch to Infisical's Enterprise plan."
/>
@@ -1,55 +1,51 @@
import {
faCheck,
faExclamationTriangle,
faHourglass,
faRotate,
IconDefinition
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
AlertTriangleIcon,
CheckIcon,
HourglassIcon,
LucideIcon,
RefreshCwIcon
} from "lucide-react";
import { Badge, BadgeProps } from "@app/components/v2/Badge/Badge";
import { Badge, TBadgeProps } from "@app/components/v3";
import { SecretSyncStatus } from "@app/hooks/api/secretSyncs";
type Props = {
status: SecretSyncStatus;
} & Omit<BadgeProps, "children" | "variant">;
} & Omit<TBadgeProps, "children" | "variant">;
export const SecretSyncStatusBadge = ({ status }: Props) => {
let variant: BadgeProps["variant"];
let variant: TBadgeProps["variant"];
let text: string;
let icon: IconDefinition;
let Icon: LucideIcon;
switch (status) {
case SecretSyncStatus.Failed:
variant = "danger";
text = "Failed to Sync";
icon = faExclamationTriangle;
Icon = AlertTriangleIcon;
break;
case SecretSyncStatus.Succeeded:
variant = "success";
text = "Synced";
icon = faCheck;
Icon = CheckIcon;
break;
case SecretSyncStatus.Pending:
variant = "primary";
variant = "info";
text = "Queued";
icon = faHourglass;
Icon = HourglassIcon;
break;
case SecretSyncStatus.Running:
default:
variant = "primary";
variant = "info";
text = "Syncing";
icon = faRotate;
Icon = RefreshCwIcon;
break;
}
return (
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap" variant={variant}>
<FontAwesomeIcon
icon={icon}
className={[SecretSyncStatus.Running].includes(status) ? "animate-spin" : ""}
/>
<span>{text}</span>
<Badge variant={variant}>
<Icon className={[SecretSyncStatus.Running].includes(status) ? "animate-spin" : ""} />
{text}
</Badge>
);
};
@@ -8,13 +8,14 @@ import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Switch } from "@app/components/v2";
import { useProject } from "@app/context";
import { useOrganization, useProject } from "@app/context";
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import {
SecretSync,
SecretSyncInitialSyncBehavior,
TSecretSync,
useCreateSecretSync,
useDuplicateDestinationCheck,
useSecretSyncOption
} from "@app/hooks/api/secretSyncs";
@@ -48,6 +49,7 @@ export const CreateSecretSyncForm = ({
}: Props) => {
const createSecretSync = useCreateSecretSync();
const { currentProject } = useProject();
const { currentOrg } = useOrganization();
const { name: destinationName } = SECRET_SYNC_MAP[destination];
const [showConfirmation, setShowConfirmation] = useState(false);
@@ -106,11 +108,20 @@ export const CreateSecretSyncForm = ({
setSelectedTabIndex((prev) => prev - 1);
};
const { handleSubmit, trigger, control } = formMethods;
const { handleSubmit, trigger, control, watch } = formMethods;
const { hasDuplicate } = useDuplicateDestinationCheck({
destination,
projectId: currentProject?.id || "",
enabled: true,
destinationConfig: watch("destinationConfig")
});
const isStepValid = async (index: number) => trigger(FORM_TABS[index].fields);
const isFinalStep = selectedTabIndex === FORM_TABS.length - 1;
const isCreateButtonDisabled =
isFinalStep && hasDuplicate && currentOrg?.blockDuplicateSecretSyncDestinations;
const handleNext = async () => {
if (isFinalStep) {
@@ -245,7 +256,7 @@ export const CreateSecretSyncForm = ({
</FormProvider>
<div className="flex w-full flex-row-reverse justify-between gap-4 pt-4">
<Button onClick={handleNext} colorSchema="secondary">
<Button onClick={handleNext} colorSchema="secondary" isDisabled={isCreateButtonDisabled}>
{isFinalStep ? "Create Sync" : "Next"}
</Button>
{selectedTabIndex > 0 && (
@@ -6,6 +6,7 @@ type Props = {
onConfirm: () => void;
isLoading?: boolean;
duplicateProjectId?: string;
isDisabled?: boolean;
};
export const DuplicateDestinationConfirmationModal = ({
@@ -13,7 +14,8 @@ export const DuplicateDestinationConfirmationModal = ({
onOpenChange,
onConfirm,
isLoading,
duplicateProjectId
duplicateProjectId,
isDisabled
}: Props) => {
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
@@ -21,7 +23,12 @@ export const DuplicateDestinationConfirmationModal = ({
<div className="mb-4 text-sm">
<p>
Another secret sync in your organization is already configured with the same
destination. Proceeding may cause conflicts or overwrite existing data.
destination.{" "}
<span className={isDisabled ? "text-red-400" : ""}>
{isDisabled
? "Your organization does not allow duplicate destination configurations."
: "Proceeding may cause conflicts or overwrite existing data."}
</span>
</p>
{duplicateProjectId && (
<p className="mt-2 text-xs text-mineshaft-400">
@@ -31,26 +38,28 @@ export const DuplicateDestinationConfirmationModal = ({
</code>
</p>
)}
<p className="mt-2">Are you sure you want to continue?</p>
{!isDisabled && <p className="mt-2">Are you sure you want to continue?</p>}
</div>
<div className="flex items-center gap-4 pt-4">
<ModalClose asChild>
<Button
onClick={onConfirm}
colorSchema="danger"
isLoading={isLoading}
isDisabled={isLoading}
>
Continue
</Button>
</ModalClose>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain" isDisabled={isLoading}>
Cancel
</Button>
</ModalClose>
</div>
{!isDisabled && (
<div className="flex items-center gap-4 pt-4">
<ModalClose asChild>
<Button
onClick={onConfirm}
colorSchema="danger"
isLoading={isLoading}
isDisabled={isLoading}
>
Continue
</Button>
</ModalClose>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain" isDisabled={isLoading}>
Cancel
</Button>
</ModalClose>
</div>
)}
</ModalContent>
</Modal>
);
@@ -5,6 +5,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { createNotification } from "@app/components/notifications";
import { SecretSyncEditFields } from "@app/components/secret-syncs/types";
import { Button, ModalClose } from "@app/components/v2";
import { useOrganization } from "@app/context";
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import {
TSecretSync,
@@ -30,6 +31,7 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) =>
const { name: destinationName } = SECRET_SYNC_MAP[secretSync.destination];
const [showDuplicateConfirmation, setShowDuplicateConfirmation] = useState(false);
const [pendingFormData, setPendingFormData] = useState<TSecretSyncForm | null>(null);
const { currentOrg } = useOrganization();
const formMethods = useForm<TSecretSyncForm>({
resolver: zodResolver(UpdateSecretSyncFormSchema),
@@ -209,6 +211,7 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) =>
onConfirm={handleConfirmDuplicate}
isLoading={updateSecretSync.isPending}
duplicateProjectId={storedDuplicateProjectId}
isDisabled={currentOrg?.blockDuplicateSecretSyncDestinations}
/>
</>
);
@@ -5,14 +5,8 @@ import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
import {
Badge,
FilterableSelect,
FormControl,
Select,
SelectItem,
Tooltip
} from "@app/components/v2";
import { FilterableSelect, FormControl, Select, SelectItem, Tooltip } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { GCP_SYNC_SCOPES } from "@app/helpers/secretSyncs";
import {
useGcpConnectionListProjectLocations,
@@ -26,10 +20,7 @@ import { TSecretSyncForm } from "../schemas";
const formatOptionLabel = ({ displayName, locationId }: TGcpLocation) => (
<div className="flex w-full flex-row items-center gap-1">
<span>{displayName}</span>{" "}
<Badge className="h-5 leading-5" variant="success">
{locationId}
</Badge>
<span>{displayName}</span> <Badge variant="info">{locationId}</Badge>
</div>
);
@@ -2,7 +2,8 @@ import { components, OptionProps, SingleValue } from "react-select";
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Badge, FilterableSelect } from "@app/components/v2";
import { FilterableSelect } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { AWS_REGIONS } from "@app/helpers/appConnections";
const Option = ({ isSelected, children, ...props }: OptionProps<(typeof AWS_REGIONS)[number]>) => {
@@ -10,7 +11,7 @@ const Option = ({ isSelected, children, ...props }: OptionProps<(typeof AWS_REGI
<components.Option isSelected={isSelected} {...props}>
<div className="flex flex-row items-center justify-between">
<p className="truncate">{children}</p>
<Badge variant="success" className="mr-auto ml-1 cursor-pointer">
<Badge variant="neutral" className="mr-auto ml-1">
{props.data.slug}
</Badge>
{isSelected && (
@@ -1,10 +1,10 @@
import { useFormContext } from "react-hook-form";
import { faEye } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { EyeIcon } from "lucide-react";
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
import { Badge, Table, TBody, Td, Th, THead, Tooltip, Tr } from "@app/components/v2";
import { Table, TBody, Td, Th, THead, Tooltip, Tr } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { AWS_REGIONS } from "@app/helpers/appConnections";
import { SecretSync } from "@app/hooks/api/secretSyncs";
@@ -41,11 +41,9 @@ export const AwsParameterStoreSyncOptionsReviewFields = () => {
}
>
<div className="w-min">
<Badge className="flex h-5 w-min items-center gap-1.5 bg-mineshaft-400/50 whitespace-nowrap text-bunker-300">
<FontAwesomeIcon icon={faEye} />
<span>
{tags.length} Tag{tags.length > 1 ? "s" : ""}
</span>
<Badge variant="neutral">
<EyeIcon />
{tags.length} Tag{tags.length > 1 ? "s" : ""}
</Badge>
</div>
</Tooltip>
@@ -1,10 +1,10 @@
import { useFormContext } from "react-hook-form";
import { faEye } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { EyeIcon } from "lucide-react";
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
import { Badge, Table, TBody, Td, Th, THead, Tooltip, Tr } from "@app/components/v2";
import { Table, TBody, Td, Th, THead, Tooltip, Tr } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { AWS_REGIONS } from "@app/helpers/appConnections";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { AwsSecretsManagerSyncMappingBehavior } from "@app/hooks/api/secretSyncs/types/aws-secrets-manager-sync";
@@ -26,7 +26,7 @@ export const AwsSecretsManagerSyncReviewFields = () => {
<>
<GenericFieldLabel label="Region">
{awsRegion?.name}
<Badge className="ml-1" variant="success">
<Badge className="ml-1" variant="info">
{awsRegion?.slug}{" "}
</Badge>
</GenericFieldLabel>
@@ -73,11 +73,9 @@ export const AwsSecretsManagerSyncOptionsReviewFields = () => {
}
>
<div className="w-min">
<Badge className="flex h-5 w-min items-center gap-1.5 bg-mineshaft-400/50 whitespace-nowrap text-bunker-300">
<FontAwesomeIcon icon={faEye} />
<span>
{tags.length} Tag{tags.length > 1 ? "s" : ""}
</span>
<Badge variant="neutral">
<EyeIcon />
{tags.length} Tag{tags.length > 1 ? "s" : ""}
</Badge>
</div>
</Tooltip>
@@ -2,7 +2,7 @@ import { useFormContext } from "react-hook-form";
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
import { Badge } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { RenderSyncScope } from "@app/hooks/api/secretSyncs/types/render-sync";
@@ -5,8 +5,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
import { Badge } from "@app/components/v2";
import { useProject } from "@app/context";
import { Badge } from "@app/components/v3";
import { useOrganization, useProject } from "@app/context";
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { SecretSync, useDuplicateDestinationCheck } from "@app/hooks/api/secretSyncs";
@@ -51,6 +51,7 @@ import { ZabbixSyncReviewFields } from "./ZabbixSyncReviewFields";
export const SecretSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm>();
const { currentProject } = useProject();
const { currentOrg } = useOrganization();
let DestinationFieldsComponent: ReactNode;
let AdditionalSyncOptionsFieldsComponent: ReactNode;
@@ -193,18 +194,50 @@ export const SecretSyncReviewFields = () => {
{isChecking && <span className="text-xs text-mineshaft-400">Checking...</span>}
</div>
{hasDuplicate && (
<div className="mb-2 flex items-start rounded-md border border-yellow-600 bg-yellow-900/20 px-3 py-2">
<div className="flex text-sm text-yellow-100">
<FontAwesomeIcon icon={faWarning} className="mt-1 mr-2 text-yellow-600" />
<div
className={`mb-2 flex items-start rounded-md border px-3 py-2 ${
currentOrg?.blockDuplicateSecretSyncDestinations
? "border-red-600 bg-red-900/20"
: "border-yellow-600 bg-yellow-900/20"
}`}
>
<div
className={`flex text-sm ${
currentOrg?.blockDuplicateSecretSyncDestinations
? "text-red-100"
: "text-yellow-100"
}`}
>
<FontAwesomeIcon
icon={faWarning}
className={`mt-1 mr-2 ${
currentOrg?.blockDuplicateSecretSyncDestinations
? "text-red-600"
: "text-yellow-600"
}`}
/>
<div>
<p>
Another secret sync in your organization is already configured with the same
destination. This may lead to conflicts or unexpected behavior.
{currentOrg?.blockDuplicateSecretSyncDestinations
? "Another secret sync in your organization is already configured with the same destination. Your organization does not allow duplicate destination configurations."
: "Another secret sync in your organization is already configured with the same destination. This may lead to conflicts or unexpected behavior."}
</p>
{duplicateProjectId && (
<p className="mt-1 text-xs text-yellow-200">
<p
className={`mt-1 text-xs ${
currentOrg?.blockDuplicateSecretSyncDestinations
? "text-red-200"
: "text-yellow-200"
}`}
>
Duplicate found in project ID:{" "}
<code className="rounded-sm bg-yellow-800/50 px-1 py-0.5">
<code
className={`rounded-sm px-1 py-0.5 ${
currentOrg?.blockDuplicateSecretSyncDestinations
? "bg-red-800/50"
: "bg-yellow-800/50"
}`}
>
{duplicateProjectId}
</code>
</p>
@@ -224,7 +257,7 @@ export const SecretSyncReviewFields = () => {
</div>
<div className="flex flex-wrap gap-x-8 gap-y-2">
<GenericFieldLabel label="Auto-Sync">
<Badge variant={isAutoSyncEnabled ? "success" : "danger"}>
<Badge variant={isAutoSyncEnabled ? "success" : "neutral"}>
{isAutoSyncEnabled ? "Enabled" : "Disabled"}
</Badge>
</GenericFieldLabel>
@@ -235,7 +268,7 @@ export const SecretSyncReviewFields = () => {
{AdditionalSyncOptionsFieldsComponent}
{disableSecretDeletion && (
<GenericFieldLabel label="Secret Deletion">
<Badge variant="primary">Disabled</Badge>
<Badge variant="warning">Disabled</Badge>
</GenericFieldLabel>
)}
</div>
@@ -1,47 +0,0 @@
import { forwardRef } from "react";
import { cva, VariantProps } from "cva";
import { twMerge } from "tailwind-merge";
interface IProps {
children: React.ReactNode;
className?: string;
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
}
const badgeVariants = cva(
[
"inline-block cursor-default rounded-md bg-yellow/20 px-1.5 py-0.5 text-xs text-yellow opacity-80 hover:opacity-100"
],
{
variants: {
variant: {
primary: "bg-yellow/20 text-yellow",
danger: "bg-red/20 text-red",
success: "bg-green/20 text-green",
org: "bg-org-v1/20 text-org-v1 [&_svg]:text-org-v1 flex items-center opacity-100 hover:bg-org-v1/10 [&_svg]:size-3 gap-x-1 w-min whitespace-nowrap",
namespace:
"bg-namespace-v1/20 text-namespace-v1 [&_svg]:text-namespace-v1 flex opacity-100 hover:bg-namespace-v1/10 items-center [&_svg]:size-3.5 gap-x-1.5 w-min whitespace-nowrap",
project:
"bg-primary/10 text-primary [&_svg]:text-primary opacity-100 hover:bg-primary/10 flex items-center [&_svg]:size-3 w-min gap-x-1.5 whitespace-nowrap",
instance:
"bg-mineshaft-200/20 text-mineshaft-200 [&_svg]:text-mineshaft-200 opacity-100 hover:bg-mineshaft-200/20 flex items-center [&_svg]:size-3 gap-x-1.5 w-min whitespace-nowrap"
}
}
}
);
export type BadgeProps = VariantProps<typeof badgeVariants> & IProps;
export const Badge = forwardRef<HTMLDivElement, BadgeProps>(
({ children, className, variant, ...props }, ref) => {
return (
<div
className={twMerge(badgeVariants({ variant: variant || "primary" }), className)}
{...props}
ref={ref}
>
{children}
</div>
);
}
);
@@ -1 +0,0 @@
export { Badge } from "./Badge";
@@ -1,11 +1,16 @@
import { IconDefinition } from "@fortawesome/free-brands-svg-icons";
import { faCube, faCubes, faGlobe, faServer } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createElement } from "react";
import { ReactNode } from "@tanstack/react-router";
import { LucideIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { Badge } from "@app/components/v2";
import { BadgeProps } from "@app/components/v2/Badge/Badge";
import {
Badge,
InstanceIcon,
OrgIcon,
ProjectIcon,
SubOrgIcon,
TBadgeProps
} from "@app/components/v3";
import { ProjectType } from "@app/hooks/api/projects/types";
type Props = {
@@ -16,19 +21,19 @@ type Props = {
scope: "org" | "namespace" | "instance" | ProjectType | null;
};
const SCOPE_NAME: Record<NonNullable<Props["scope"]>, { label: string; icon: IconDefinition }> = {
org: { label: "Organization", icon: faGlobe },
[ProjectType.SecretManager]: { label: "Project", icon: faCube },
[ProjectType.CertificateManager]: { label: "Project", icon: faCube },
[ProjectType.SSH]: { label: "Project", icon: faCube },
[ProjectType.KMS]: { label: "Project", icon: faCube },
[ProjectType.PAM]: { label: "Project", icon: faCube },
[ProjectType.SecretScanning]: { label: "Project", icon: faCube },
namespace: { label: "Sub-Organization", icon: faCubes },
instance: { label: "Server", icon: faServer }
const SCOPE_NAME: Record<NonNullable<Props["scope"]>, { label: string; icon: LucideIcon }> = {
org: { label: "Organization", icon: OrgIcon },
[ProjectType.SecretManager]: { label: "Project", icon: ProjectIcon },
[ProjectType.CertificateManager]: { label: "Project", icon: ProjectIcon },
[ProjectType.SSH]: { label: "Project", icon: ProjectIcon },
[ProjectType.KMS]: { label: "Project", icon: ProjectIcon },
[ProjectType.PAM]: { label: "Project", icon: ProjectIcon },
[ProjectType.SecretScanning]: { label: "Project", icon: ProjectIcon },
namespace: { label: "Sub-Organization", icon: SubOrgIcon },
instance: { label: "Server", icon: InstanceIcon }
};
const SCOPE_VARIANT: Record<NonNullable<Props["scope"]>, BadgeProps["variant"]> = {
const SCOPE_VARIANT: Record<NonNullable<Props["scope"]>, TBadgeProps["variant"]> = {
org: "org",
[ProjectType.SecretManager]: "project",
[ProjectType.CertificateManager]: "project",
@@ -36,8 +41,8 @@ const SCOPE_VARIANT: Record<NonNullable<Props["scope"]>, BadgeProps["variant"]>
[ProjectType.KMS]: "project",
[ProjectType.PAM]: "project",
[ProjectType.SecretScanning]: "project",
namespace: "namespace",
instance: "instance"
namespace: "sub-org",
instance: "neutral"
};
export const PageHeader = ({ title, description, children, className, scope }: Props) => (
@@ -47,7 +52,7 @@ export const PageHeader = ({ title, description, children, className, scope }: P
<h1 className="text-3xl font-medium text-white capitalize">{title}</h1>
{scope && (
<Badge variant={SCOPE_VARIANT[scope]} className="mt-1 ml-2.5">
<FontAwesomeIcon icon={SCOPE_NAME[scope].icon} />
{createElement(SCOPE_NAME[scope].icon)}
{SCOPE_NAME[scope].label}
</Badge>
)}
@@ -3,6 +3,7 @@ import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { faCaretDown, faCaretUp, faCheck } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as SelectPrimitive from "@radix-ui/react-select";
import { LucideIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { Spinner } from "../Spinner";
@@ -19,6 +20,7 @@ type Props = {
icon?: IconProp;
isMulti?: boolean;
iconClassName?: string;
LucideIcon?: LucideIcon;
dropdownContainerStyle?: React.CSSProperties;
side?: SelectPrimitive.SelectContentProps["side"];
};
@@ -39,6 +41,7 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
iconClassName,
dropdownContainerStyle,
side,
LucideIcon: Lucide,
...props
},
ref
@@ -65,6 +68,7 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
>
<div className="flex items-center space-x-2 overflow-hidden text-ellipsis whitespace-nowrap">
{props.icon && <FontAwesomeIcon icon={props.icon} className={iconClassName} />}
{Lucide && <Lucide className={twMerge("size-3.5", iconClassName)} />}
<div className="flex-1 truncate">
<SelectPrimitive.Value placeholder={placeholder} />
</div>
-1
View File
@@ -2,7 +2,6 @@
export * from "./AccessRestrictedBanner";
export * from "./Accordion";
export * from "./Alert";
export * from "./Badge";
export * from "./Breadcrumb";
export * from "./Button";
export * from "./Card";
@@ -1,9 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { Link } from "@tanstack/react-router";
import {
AlertTriangleIcon,
AsteriskIcon,
BanIcon,
BoxesIcon,
BoxIcon,
CheckIcon,
ChevronsUpDownIcon,
CircleXIcon,
@@ -11,9 +11,11 @@ import {
GlobeIcon,
InfoIcon,
RadarIcon,
TriangleAlertIcon
TriangleAlertIcon,
UserIcon
} from "lucide-react";
import { OrgIcon, ProjectIcon, SubOrgIcon } from "../../platform";
import { Badge } from "./Badge";
/**
@@ -69,7 +71,29 @@ export const Neutral: Story = {
parameters: {
docs: {
description: {
story: "Use this variant when indicating neutral or disabled states."
story:
"Use this variant when indicating neutral or disabled states or when linking to external documents."
}
}
}
};
export const Ghost: Story = {
name: "Variant: Ghost",
args: {
variant: "ghost",
children: (
<>
<UserIcon />
User
</>
)
},
parameters: {
docs: {
description: {
story:
"Use this variant when indicating a configuration or property value. Avoid using this variant as an interactive element as it is not intuitive to interact with."
}
}
}
@@ -109,8 +133,7 @@ export const Info: Story = {
parameters: {
docs: {
description: {
story:
"Use this variant when indicating informational states or linking to external references."
story: "Use this variant when indicating informational states."
}
}
}
@@ -162,7 +185,7 @@ export const Organization: Story = {
variant: "org",
children: (
<>
<GlobeIcon />
<OrgIcon />
Organization
</>
)
@@ -182,7 +205,7 @@ export const SubOrganization: Story = {
variant: "sub-org",
children: (
<>
<BoxesIcon />
<SubOrgIcon />
Sub-Organization
</>
)
@@ -202,7 +225,7 @@ export const Project: Story = {
variant: "project",
children: (
<>
<BoxIcon />
<ProjectIcon />
Project
</>
)
@@ -283,10 +306,11 @@ export const AsButton: Story = {
export const IsTruncatable: Story = {
name: "Example: isTruncatable",
args: {
variant: "org",
isTruncatable: true,
children: (
<>
<GlobeIcon />
<OrgIcon />
<span>Infisical Infrastructure</span>
</>
)
@@ -295,7 +319,7 @@ export const IsTruncatable: Story = {
docs: {
description: {
story:
"Use the `isTruncatable` prop with a `span` tag wrapping the text content to support truncation."
"Use the `isTruncatable` prop with a `span` tag wrapping the text content to support truncation. Parent `div` should have a fixed width and `flex` class."
}
}
},
@@ -305,3 +329,48 @@ export const IsTruncatable: Story = {
</div>
)
};
export const IsSquare: Story = {
name: "Example: isSquare",
args: {
variant: "danger",
isSquare: true,
children: <AlertTriangleIcon />
},
parameters: {
docs: {
description: {
story:
"Use the `isSquare` prop when displaying a squared badge with 1-2 character text or only an icon."
}
}
}
};
export const IsFullWidth: Story = {
name: "Example: isFullWidth",
args: {
variant: "neutral",
isFullWidth: true,
children: (
<>
<AsteriskIcon />
Secret Value
</>
)
},
parameters: {
docs: {
description: {
story:
"Use the `isFullWidth` prop to expand the badges width to fill it's parent container."
}
}
},
decorators: (Story) => (
<div className="w-32">
<Story />
</div>
)
};
@@ -6,30 +6,37 @@ import { cn } from "@app/components/v3/utils";
const badgeVariants = cva(
[
"select-none items-center rounded-sm px-1.5 py-0.5 text-xs",
"gap-x-1 [a&,button&]:cursor-pointer inline-flex",
"[&>svg]:pointer-events-none [&>svg]:shrink-0 [&>svg]:stroke-[2.25] [&>svg]:size-3",
"select-none items-center align-middle rounded-sm h-4.5 px-1.5 text-xs",
"gap-x-1 [a&,button&]:cursor-pointer inline-flex font-normal",
"[&>svg]:pointer-events-none [&>svg]:shrink-0 [&>svg]:stroke-[2.25] [&_svg:not([class*='size-'])]:size-3",
"transition duration-200 ease-in-out"
],
{
variants: {
isTruncatable: {
true: "[&>span,&>p]:truncate min-w-0",
false: "w-fit shrink-0 whitespace-nowrap overflow-hidden"
false: "w-fit shrink-0 min-w-fit whitespace-nowrap overflow-hidden"
},
isFullWidth: {
true: "w-full justify-center"
},
isSquare: {
true: "w-4.5 justify-center px-0.5"
},
variant: {
neutral: "bg-neutral/30 text-neutral [a&,button&]:hover:bg-neutral/40",
success: "bg-success/30 text-success [a&,button&]:hover:bg-success/40",
info: "bg-info/30 text-info [a&,button&]:hover:bg-info/40",
warning: "bg-warning/30 text-warning [a&,button&]:hover:bg-warning/40",
danger: "bg-danger/30 text-danger [a&,button&]:hover:bg-danger/40",
project: "bg-project/30 text-project [a&,button&]:hover:bg-project/40",
org: "bg-org/30 text-org [a&,button&]:hover:bg-org/40",
"sub-org": "bg-sub-org/30 text-sub-org [a&,button&]:hover:bg-sub-org/40"
ghost: "text-mineshaft-200 gap-x-2",
neutral: "bg-neutral/25 text-neutral [a&,button&]:hover:bg-neutral/35",
success: "bg-success/25 text-success [a&,button&]:hover:bg-success/35",
info: "bg-info/25 text-info [a&,button&]:hover:bg-info/35",
warning: "bg-warning/25 text-warning [a&,button&]:hover:bg-warning/35",
danger: "bg-danger/25 text-danger [a&,button&]:hover:bg-danger/35",
project: "bg-project/25 text-project [a&,button&]:hover:bg-project/35",
org: "bg-org/25 text-org [a&,button&]:hover:bg-org/35",
"sub-org": "bg-sub-org/25 text-sub-org [a&,button&]:hover:bg-sub-org/35"
}
},
defaultVariants: {
variant: "success"
variant: "neutral"
}
}
);
@@ -37,16 +44,28 @@ const badgeVariants = cva(
type TBadgeProps = VariantProps<typeof badgeVariants> &
React.ComponentProps<"span"> & {
asChild?: boolean;
variant: NonNullable<VariantProps<typeof badgeVariants>["variant"]>; // TODO: REMOVE
};
const Badge = forwardRef<HTMLSpanElement, TBadgeProps>(
({ className, variant, asChild = false, isTruncatable = false, ...props }, ref): JSX.Element => {
(
{
className,
variant,
asChild = false,
isTruncatable = false,
isFullWidth = false,
isSquare = false,
...props
},
ref
): JSX.Element => {
const Comp = asChild ? Slot : "span";
return (
<Comp
ref={ref}
data-slot="badge"
className={cn(badgeVariants({ variant, isTruncatable }), className)}
className={cn(badgeVariants({ variant, isTruncatable, isFullWidth, isSquare }), className)}
{...props}
/>
);
+2
View File
@@ -0,0 +1,2 @@
export * from "./generic";
export * from "./platform";
@@ -0,0 +1,24 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { DocumentationLinkBadge } from "./DocumentationLinkBadge";
/**
* The documentation link badge is a re-usable component to link to Infisical documentation references.
*/
const meta = {
title: "Platform/Documentation Link Badge",
component: DocumentationLinkBadge,
parameters: {
layout: "centered"
},
tags: ["autodocs"],
argTypes: {},
args: { href: "https://infisical.com/docs/documentation/getting-started/introduction" }
} satisfies Meta<typeof DocumentationLinkBadge>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
name: "Example: Default"
};
@@ -0,0 +1,18 @@
import { BookOpenIcon } from "lucide-react";
import { Badge } from "@app/components/v3";
type TDocumentationLinkBadgeProps = {
href: string;
};
export function DocumentationLinkBadge({ href }: TDocumentationLinkBadgeProps) {
return (
<Badge variant="neutral" asChild>
<a href={href} target="_blank" rel="noopener noreferrer">
<BookOpenIcon />
Documentation
</a>
</Badge>
);
}
@@ -0,0 +1 @@
export * from "./DocumentationLinkBadge";
@@ -0,0 +1,8 @@
import { BoxesIcon, BoxIcon, Building2Icon, ServerIcon } from "lucide-react";
const InstanceIcon = ServerIcon;
const OrgIcon = Building2Icon;
const SubOrgIcon = BoxesIcon;
const ProjectIcon = BoxIcon;
export { InstanceIcon, OrgIcon, ProjectIcon, SubOrgIcon };
@@ -0,0 +1,2 @@
export * from "./DocumentationLinkBadge";
export * from "./ScopeIcons";