Merge branch 'main' into feature/slack-secret-sync-error-notification

This commit is contained in:
Victor Santos
2025-11-03 15:55:10 -03:00
679 changed files with 19636 additions and 8864 deletions
@@ -105,7 +105,7 @@ export const IPAllowlistSection = () => {
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can use IP allowlisting if you switch to Infisical's Pro plan."
text="Your current plan does not include access to IP allowlisting. To unlock this feature, please upgrade to Infisical Pro plan."
/>
</div>
);
@@ -159,7 +159,7 @@ export const IPAllowlistTable = ({ popUp, handlePopUpOpen, handlePopUpToggle }:
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can use IP allowlisting if you switch to Infisical's Pro plan."
text="Your current plan does not include access to IP allowlisting. To unlock this feature, please upgrade to Infisical Pro plan."
/>
</div>
);
@@ -14,7 +14,8 @@ import { format } from "date-fns";
import { twMerge } from "tailwind-merge";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Badge, IconButton, Td, Tooltip, Tr } from "@app/components/v2";
import { IconButton, Td, Tooltip, Tr } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
import { TCloudIntegration } from "@app/hooks/api/integrations/types";
import { TIntegration } from "@app/hooks/api/types";
@@ -103,7 +104,7 @@ export const IntegrationRow = ({
<Td>
{" "}
{typeof integration.isSynced !== "boolean" ? (
<Badge variant="primary" key={integration.id}>
<Badge variant="info" key={integration.id}>
Pending Sync
</Badge>
) : (
@@ -0,0 +1,14 @@
import { TChefSync } from "@app/hooks/api/secretSyncs/types/chef-sync";
import { getSecretSyncDestinationColValues } from "../helpers";
import { SecretSyncTableCell } from "../SecretSyncTableCell";
type Props = {
secretSync: TChefSync;
};
export const ChefSyncDestinationCol = ({ secretSync }: Props) => {
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
};
@@ -0,0 +1,14 @@
import { TNorthflankSync } from "@app/hooks/api/secretSyncs/types/northflank-sync";
import { getSecretSyncDestinationColValues } from "../helpers";
import { SecretSyncTableCell } from "../SecretSyncTableCell";
type Props = {
secretSync: TNorthflankSync;
};
export const NorthflankSyncDestinationCol = ({ secretSync }: Props) => {
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
};
@@ -9,6 +9,7 @@ import { AzureKeyVaultDestinationSyncCol } from "./AzureKeyVaultDestinationSyncC
import { BitbucketSyncDestinationCol } from "./BitbucketSyncDestinationCol";
import { CamundaSyncDestinationCol } from "./CamundaSyncDestinationCol";
import { ChecklySyncDestinationCol } from "./ChecklySyncDestinationCol";
import { ChefSyncDestinationCol } from "./ChefSyncDestinationCol";
import { CloudflarePagesSyncDestinationCol } from "./CloudflarePagesSyncDestinationCol";
import { CloudflareWorkersSyncDestinationCol } from "./CloudflareWorkersSyncDestinationCol";
import { DatabricksSyncDestinationCol } from "./DatabricksSyncDestinationCol";
@@ -22,6 +23,7 @@ import { HerokuSyncDestinationCol } from "./HerokuSyncDestinationCol";
import { HumanitecSyncDestinationCol } from "./HumanitecSyncDestinationCol";
import { LaravelForgeSyncDestinationCol } from "./LaravelForgeSyncDestinationCol";
import { NetlifySyncDestinationCol } from "./NetlifySyncDestinationCol";
import { NorthflankSyncDestinationCol } from "./NorthflankSyncDestinationCol";
import { OCIVaultSyncDestinationCol } from "./OCIVaultSyncDestinationCol";
import { RailwaySyncDestinationCol } from "./RailwaySyncDestinationCol";
import { RenderSyncDestinationCol } from "./RenderSyncDestinationCol";
@@ -96,10 +98,14 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => {
return <DigitalOceanAppPlatformSyncDestinationCol secretSync={secretSync} />;
case SecretSync.Netlify:
return <NetlifySyncDestinationCol secretSync={secretSync} />;
case SecretSync.Northflank:
return <NorthflankSyncDestinationCol secretSync={secretSync} />;
case SecretSync.Bitbucket:
return <BitbucketSyncDestinationCol secretSync={secretSync} />;
case SecretSync.LaravelForge:
return <LaravelForgeSyncDestinationCol secretSync={secretSync} />;
case SecretSync.Chef:
return <ChefSyncDestinationCol secretSync={secretSync} />;
default:
throw new Error(
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
@@ -13,12 +13,12 @@ import {
faToggleOff,
faToggleOn,
faTrash,
faTriangleExclamation,
faXmark
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import { format } from "date-fns";
import { AlertTriangleIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
@@ -29,7 +29,6 @@ import {
SecretSyncStatusBadge
} from "@app/components/secret-syncs";
import {
Badge,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
@@ -39,6 +38,7 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { ROUTE_PATHS } from "@app/const/routes";
import { ProjectPermissionSub } from "@app/context";
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
@@ -173,15 +173,10 @@ export const SecretSyncRow = ({
) : (
<Td>
<Tooltip content="The source location for this sync has been deleted. Configure a new source or remove this sync.">
<div className="w-min">
<Badge
className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap"
variant="primary"
>
<FontAwesomeIcon icon={faTriangleExclamation} />
<span>Source Folder Deleted</span>
</Badge>
</div>
<Badge variant="danger">
<AlertTriangleIcon />
<span>Source Folder Deleted</span>
</Badge>
</Tooltip>
</Td>
)}
@@ -237,7 +232,7 @@ export const SecretSyncRow = ({
content="Auto-Sync is disabled. Changes to the source location will not be automatically synced to the destination."
>
<div>
<Badge className="flex h-5 w-min items-center gap-1.5 bg-mineshaft-400/50 whitespace-nowrap text-bunker-300">
<Badge variant="neutral">
<FontAwesomeIcon icon={faBan} />
{!syncStatus && "Auto-Sync Disabled"}
</Badge>
@@ -194,10 +194,18 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
primaryText = destinationConfig.workspaceSlug;
secondaryText = destinationConfig.repositorySlug;
break;
case SecretSync.Northflank:
primaryText = destinationConfig.projectName || destinationConfig.projectId;
secondaryText = destinationConfig.secretGroupName || destinationConfig.secretGroupId;
break;
case SecretSync.LaravelForge:
primaryText = destinationConfig.siteName || destinationConfig.siteId;
secondaryText = destinationConfig.orgName || destinationConfig.orgSlug;
break;
case SecretSync.Chef:
primaryText = destinationConfig.dataBagName;
secondaryText = destinationConfig.dataBagItemName;
break;
default:
throw new Error(`Unhandled Destination Col Values ${destination}`);
}
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useSearch } from "@tanstack/react-router";
@@ -7,6 +7,7 @@ import { ProjectPermissionCan } from "@app/components/permissions";
import { CreateSecretSyncModal } from "@app/components/secret-syncs";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
import { Button, Spinner } from "@app/components/v2";
import { DocumentationLinkBadge } from "@app/components/v3";
import { ROUTE_PATHS } from "@app/const/routes";
import { ProjectPermissionSub, useProject } from "@app/context";
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
@@ -91,22 +92,9 @@ export const SecretSyncsTab = () => {
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
<div>
<div className="flex items-start gap-1">
<div className="flex items-center gap-1.5">
<p className="text-lg font-medium text-mineshaft-100">Secret Syncs</p>
<a
href="https://infisical.com/docs/integrations/secret-syncs/overview"
target="_blank"
rel="noopener noreferrer"
>
<div className="mt-[0.32rem] ml-1 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.5" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-[10px]"
/>
</div>
</a>
<DocumentationLinkBadge href="https://infisical.com/docs/integrations/secret-syncs/overview" />
</div>
<p className="text-sm text-bunker-300">
Use App Connections to sync secrets to third-party services.
@@ -277,7 +277,11 @@ export const OverviewPage = () => {
});
const isFilteredByResources = Object.values(filter).some(Boolean);
const { isPending: isOverviewLoading, data: overview } = useGetProjectSecretsOverview(
const {
isPending: isOverviewLoading,
data: overview,
isFetching: isOverviewFetching
} = useGetProjectSecretsOverview(
{
projectId,
environments: visibleEnvs.map((env) => env.slug),
@@ -631,6 +635,8 @@ export const OverviewPage = () => {
};
const handleFolderClick = (path: string) => {
if (isOverviewFetching) return;
// store for breadcrumb nav to restore previously used filters
setFilterHistory((prev) => {
const curr = new Map(prev);
@@ -1173,7 +1179,8 @@ export const OverviewPage = () => {
return;
}
handlePopUpOpen("upgradePlan", {
isEnterpriseFeature: true
isEnterpriseFeature: true,
text: "Adding dynamic secrets can be unlocked if you upgrade to Infisical Enterprise plan."
});
}}
isDisabled={userAvailableDynamicSecretEnvs.length === 0}
@@ -1197,7 +1204,9 @@ export const OverviewPage = () => {
handlePopUpClose("misc");
return;
}
handlePopUpOpen("upgradePlan");
handlePopUpOpen("upgradePlan", {
text: "Adding secret rotations can be unlocked if you upgrade to Infisical Pro plan."
});
}}
isDisabled={userAvailableSecretRotationEnvs.length === 0}
variant="outline_bg"
@@ -1682,11 +1691,7 @@ export const OverviewPage = () => {
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature}
text={
subscription.slug === null
? "You can perform this action under an Enterprise license"
: "You can perform this action if you switch to Infisical's Enterprise plan"
}
text={popUp.upgradePlan.data?.text}
/>
)}
<CreateSecretRotationV2Modal
@@ -11,11 +11,13 @@ import {
faXmark
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { XIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { ProjectPermissionCan } from "@app/components/permissions";
import { SecretRotationV2StatusBadge } from "@app/components/secret-rotations-v2/SecretRotationV2StatusBadge";
import { Badge, IconButton, TableContainer, Tag, Td, Tooltip, Tr } from "@app/components/v2";
import { IconButton, TableContainer, Tag, Td, Tooltip, Tr } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import {
ProjectPermissionSecretRotationActions,
ProjectPermissionSub
@@ -70,15 +72,10 @@ export const SecretOverviewSecretRotationRow = ({
<div className="flex-1">{secretRotationName}</div>
{statuses?.some((status) => status === SecretRotationStatus.Failed) && (
<Tooltip className="max-w-sm" content="One or more secrets failed to rotate.">
<div>
<Badge
variant="danger"
className="flex h-5 w-min items-center gap-1 whitespace-nowrap"
>
<FontAwesomeIcon icon={faXmark} />
<span>Rotation Failed</span>
</Badge>
</div>
<Badge variant="danger">
<XIcon />
Rotation Failed
</Badge>
</Tooltip>
)}
</div>
@@ -1,20 +1,18 @@
import {
faCheck,
faChevronRight,
faCode,
faCopy,
faEye,
faFolder,
faKey,
faTags
faKey
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import { CurlyBracesIcon, TagsIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import {
Badge,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
@@ -25,6 +23,7 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { useProject } from "@app/context";
import { reverseTruncate } from "@app/helpers/reverseTruncate";
import { useTimedReset } from "@app/hooks";
@@ -135,15 +134,15 @@ export const QuickSearchSecretItem = ({
<Td>
<div className="flex w-full items-center justify-end gap-4">
{tagMatch && (
<Badge variant="primary" className="flex items-center gap-1 whitespace-nowrap">
<FontAwesomeIcon size="xs" icon={faTags} />
<Badge variant="neutral">
<TagsIcon />
{tagMatch.slug}
</Badge>
)}
{metadataMatch && !tagMatch && (
<Badge variant="primary" className="flex items-center gap-1 whitespace-nowrap">
<FontAwesomeIcon size="xs" icon={faCode} />
<p className="truncate">Metadata Match</p>
<Badge variant="neutral">
<CurlyBracesIcon />
Metadata
</Badge>
)}
{isSingleEnv ? (
@@ -2,7 +2,7 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { Badge } from "@app/components/v2/Badge";
import { Badge } from "@app/components/v3";
import { useProject } from "@app/context";
import { useGetAccessRequestsCount, useGetSecretApprovalRequestCount } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/projects/types";
@@ -50,13 +50,17 @@ export const SecretApprovalsPage = () => {
<Tab variant="project" value={TabSection.SecretApprovalRequests}>
Change Requests
{Boolean(secretApprovalReqCount?.open) && (
<Badge className="ml-2">{secretApprovalReqCount?.open}</Badge>
<Badge variant="warning" isSquare className="ml-2">
{secretApprovalReqCount?.open}
</Badge>
)}
</Tab>
<Tab variant="project" value={TabSection.ResourceApprovalRequests}>
Access Requests
{Boolean(accessApprovalRequestCount?.pendingCount) && (
<Badge className="ml-2">{accessApprovalRequestCount?.pendingCount}</Badge>
<Badge variant="warning" isSquare className="ml-2">
{accessApprovalRequestCount?.pendingCount}
</Badge>
)}
</Tab>
<Tab variant="project" value={TabSection.Policies}>
@@ -2,23 +2,18 @@
/* eslint-disable react/jsx-no-useless-fragment */
import { useCallback, useMemo, useState } from "react";
import {
faArrowUpRightFromSquare,
faBan,
faBookOpen,
faCheck,
faCheckCircle,
faChevronDown,
faClipboardCheck,
faLock,
faMagnifyingGlass,
faPlus,
faSearch,
faStopwatch,
faUser,
IconDefinition
faUser
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format, formatDistance } from "date-fns";
import { BanIcon, CheckIcon, ClipboardCheckIcon, LucideIcon, TimerIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
@@ -34,7 +29,7 @@ import {
Pagination,
Tooltip
} from "@app/components/v2";
import { Badge } from "@app/components/v2/Badge";
import { Badge, DocumentationLinkBadge } from "@app/components/v3";
import {
ProjectPermissionMemberActions,
ProjectPermissionSub,
@@ -207,12 +202,12 @@ export const AccessApprovalRequest = ({
let displayData: {
label: string;
type: "primary" | "danger" | "success";
type: "warning" | "danger" | "success";
tooltipContent?: string;
icon: IconDefinition | null;
icon: LucideIcon | null;
} = {
label: "",
type: "primary",
type: "warning",
icon: null
};
@@ -225,7 +220,7 @@ export const AccessApprovalRequest = ({
displayData = {
label: "Access Expired",
type: "danger",
icon: faStopwatch,
icon: TimerIcon,
tooltipContent: request.privilege?.temporaryAccessEndTime
? `Expired ${format(request.privilege.temporaryAccessEndTime, "M/d/yyyy h:mm aa")}`
: undefined
@@ -234,27 +229,27 @@ export const AccessApprovalRequest = ({
displayData = {
label: "Access Granted",
type: "success",
icon: faCheck,
icon: CheckIcon,
tooltipContent: `Granted ${format(request.updatedAt, "M/d/yyyy h:mm aa")}`
};
else if (isRejectedByAnyone)
displayData = {
label: "Rejected",
type: "danger",
icon: faBan,
icon: BanIcon,
tooltipContent: `Rejected ${format(request.updatedAt, "M/d/yyyy h:mm aa")}`
};
else if (userReviewStatus === ApprovalStatus.APPROVED) {
displayData = {
label: "Pending Additional Reviews",
type: "primary",
icon: faClipboardCheck
type: "warning",
icon: ClipboardCheckIcon
};
} else if (!isReviewedByUser)
displayData = {
label: "Review Required",
type: "primary",
icon: faClipboardCheck
type: "warning",
icon: ClipboardCheckIcon
};
return {
@@ -301,22 +296,9 @@ export const AccessApprovalRequest = ({
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
<div>
<div className="flex items-start gap-1">
<div className="flex items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">Access Requests</p>
<a
href="https://infisical.com/docs/documentation/platform/access-controls/access-requests"
target="_blank"
rel="noopener noreferrer"
>
<div className="mt-[0.32rem] ml-1 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.5" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-[10px]"
/>
</div>
</a>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/access-controls/access-requests" />
</div>
<p className="text-sm text-bunker-300">
Request and review access to secrets in sensitive environments and folders
@@ -329,7 +311,9 @@ export const AccessApprovalRequest = ({
<Button
onClick={() => {
if (subscription && !subscription?.secretApproval) {
handlePopUpOpen("upgradePlan");
handlePopUpOpen("upgradePlan", {
text: "Access requests feature can be unlocked if you upgrade to Infisical Pro plan."
});
return;
}
handlePopUpOpen("requestAccess");
@@ -473,6 +457,8 @@ export const AccessApprovalRequest = ({
filteredRequests?.slice(offset, perPage * page).map((request) => {
const details = generateRequestDetails(request);
const StatusIcon = details.displayData.icon;
return (
<div
key={request.id}
@@ -486,7 +472,7 @@ export const AccessApprovalRequest = ({
}
}}
>
<div className="flex w-full items-center justify-between">
<div className="flex flex-1 items-center justify-between">
<div className="flex w-full flex-col justify-between">
<div className="mb-1 flex w-full items-center">
<FontAwesomeIcon
@@ -510,7 +496,7 @@ export const AccessApprovalRequest = ({
</div>
</div>
</div>
<div className="flex items-center gap-3">
<div className="flex shrink-0 items-center gap-3">
{request.requestedByUserId === user.id && (
<div className="flex items-center gap-1.5 text-xs whitespace-nowrap text-bunker-300">
<FontAwesomeIcon icon={faUser} size="sm" />
@@ -518,17 +504,10 @@ export const AccessApprovalRequest = ({
</div>
)}
<Tooltip content={details.displayData.tooltipContent}>
<div>
<Badge
className="flex items-center gap-1.5 whitespace-nowrap"
variant={details.displayData.type}
>
{details.displayData.icon && (
<FontAwesomeIcon icon={details.displayData.icon} />
)}
<span>{details.displayData.label}</span>
</Badge>
</div>
<Badge variant={details.displayData.type}>
{StatusIcon && <StatusIcon />}
{details.displayData.label}
</Badge>
</Tooltip>
</div>
</div>
@@ -593,7 +572,7 @@ export const AccessApprovalRequest = ({
)}
<UpgradePlanModal
text="You need to upgrade your plan to access this feature"
text={popUp.upgradePlan.data?.text}
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={() => handlePopUpClose("upgradePlan")}
/>
@@ -9,6 +9,7 @@ import {
faUserSlash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { BanIcon, CheckIcon, HourglassIcon } from "lucide-react";
import ms from "ms";
import { twMerge } from "tailwind-merge";
@@ -24,7 +25,7 @@ import {
ModalContent,
Tooltip
} from "@app/components/v2";
import { Badge } from "@app/components/v2/Badge";
import { Badge } from "@app/components/v3";
import { ProjectPermissionActions, useProject, useUser } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useListWorkspaceGroups, useReviewAccessRequest } from "@app/hooks/api";
@@ -42,13 +43,13 @@ import { EditAccessRequestModal } from "@app/pages/secret-manager/SecretApproval
const getReviewedStatusSymbol = (status?: ApprovalStatus, isOrgMembershipActive?: boolean) => {
if (status === ApprovalStatus.APPROVED)
return (
<Badge variant="success" className="flex h-4 items-center justify-center">
<Badge variant="success">
<FontAwesomeIcon icon={faCheck} size="xs" />
</Badge>
);
if (status === ApprovalStatus.REJECTED)
return (
<Badge variant="danger" className="flex h-4 items-center justify-center">
<Badge variant="danger">
<FontAwesomeIcon icon={faBan} size="xs" />
</Badge>
);
@@ -58,13 +59,13 @@ const getReviewedStatusSymbol = (status?: ApprovalStatus, isOrgMembershipActive?
// Can't do a tooltip here because nested tooltips doesn't work properly as of yet.
// TODO(daniel): Fix nested tooltips in the future.
<Badge className="flex h-4 items-center justify-center bg-mineshaft-400/50 text-bunker-300">
<Badge variant="neutral">
<FontAwesomeIcon size="xs" icon={faUserSlash} />
</Badge>
);
}
return (
<Badge variant="primary" className="flex h-4 items-center justify-center">
<Badge variant="warning">
<FontAwesomeIcon icon={faHourglass} size="xs" />
</Badge>
);
@@ -346,9 +347,7 @@ export const ReviewAccessRequestModal = ({
<span>Approvers</span>
{approverSequence.isMyReviewInThisSequence &&
request.status === ApprovalStatus.PENDING && (
<Badge variant="primary" className="h-min">
Awaiting Your Review
</Badge>
<Badge variant="warning">Awaiting Your Review</Badge>
)}
</div>
<div className="max-h-[40vh] thin-scrollbar overflow-y-auto rounded-sm py-2">
@@ -364,43 +363,28 @@ export const ReviewAccessRequestModal = ({
let BadgeComponent: ReactNode = null;
if (approver.hasRejected) {
StepComponent = (
<Badge
variant="danger"
className="flex h-6 min-w-6 items-center justify-center"
>
<FontAwesomeIcon icon={faBan} />
<Badge isSquare variant="danger">
<BanIcon />
</Badge>
);
BadgeComponent = <Badge variant="danger">Rejected</Badge>;
} else if (approver.hasApproved) {
StepComponent = (
<Badge
variant="success"
className="flex h-6 min-w-6 items-center justify-center"
>
<FontAwesomeIcon icon={faCheck} />
<Badge isSquare variant="success">
<CheckIcon />
</Badge>
);
BadgeComponent = <Badge variant="success">Approved</Badge>;
} else if (isPending) {
StepComponent = (
<Badge
variant="primary"
className="flex h-6 min-w-6 items-center justify-center"
>
<FontAwesomeIcon icon={faHourglass} />
<Badge isSquare variant="warning">
<HourglassIcon />
</Badge>
);
BadgeComponent = <Badge variant="primary">Pending</Badge>;
BadgeComponent = <Badge variant="warning">Pending</Badge>;
} else {
StepComponent = (
<Badge
className={
isInactive
? "py-auto my-auto flex h-6 min-w-6 items-center justify-center gap-1.5 bg-mineshaft-400/50 text-center whitespace-nowrap text-bunker-200"
: ""
}
>
<Badge isSquare variant="neutral">
<span>{index + 1}</span>
</Badge>
);
@@ -447,12 +431,10 @@ export const ReviewAccessRequestModal = ({
{member.user.username}
<span className="text-xs">
<Tooltip content="This user has been deactivated and no longer has an active organization membership.">
<div>
<Badge className="pointer-events-none mr-auto ml-1 flex h-5 w-min items-center gap-1.5 bg-mineshaft-400/50 whitespace-nowrap text-bunker-300">
<FontAwesomeIcon icon={faBan} />
Inactive
</Badge>
</div>
<Badge variant="neutral">
<BanIcon />
Inactive
</Badge>
</Tooltip>
</span>
</span>
@@ -2,8 +2,6 @@ import { useMemo, useState } from "react";
import {
faArrowDown,
faArrowUp,
faArrowUpRightFromSquare,
faBookOpen,
faCheckCircle,
faFileShield,
faFilter,
@@ -36,6 +34,7 @@ import {
THead,
Tr
} from "@app/components/v2";
import { DocumentationLinkBadge } from "@app/components/v3";
import {
ProjectPermissionSub,
TProjectPermission,
@@ -243,22 +242,9 @@ export const ApprovalPolicyList = ({ projectId }: IProps) => {
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
<div>
<div className="flex items-start gap-1">
<div className="flex items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">Policies</p>
<a
href="https://infisical.com/docs/documentation/platform/pr-workflows"
target="_blank"
rel="noopener noreferrer"
>
<div className="mt-[0.32rem] ml-1 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.5" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-[10px]"
/>
</div>
</a>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/pr-workflows" />
</div>
<p className="text-sm text-bunker-300">
Implement granular policies for access requests and secrets management
@@ -505,7 +491,7 @@ export const ApprovalPolicyList = ({ projectId }: IProps) => {
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can add secret approval policy if you switch to Infisical's Enterprise plan."
text="Adding secret approval policies can be unlocked if you upgrade to Infisical Pro plan."
/>
</>
);
@@ -1,6 +1,5 @@
import { useMemo } from "react";
import {
faBan,
faClipboardCheck,
faEdit,
faEllipsisV,
@@ -9,6 +8,7 @@ import {
faUserGroup
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { BanIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { ProjectPermissionCan } from "@app/components/permissions";
@@ -23,7 +23,7 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
import { Badge } from "@app/components/v2/Badge";
import { Badge } from "@app/components/v3";
import { ProjectPermissionSub } from "@app/context";
import { ProjectPermissionActions } from "@app/context/ProjectPermissionContext/types";
import { getMemberLabel } from "@app/helpers/members";
@@ -100,6 +100,8 @@ export const ApprovalPolicyRow = ({
});
}, [policy, members, groups]);
const { variant, Icon } = policyDetails[policy.policyType];
return (
<>
<Tr
@@ -116,13 +118,8 @@ export const ApprovalPolicyRow = ({
<Td>{policy.environments.map((env) => env.name).join(", ")}</Td>
<Td>{policy.secretPath || "*"}</Td>
<Td>
<Badge
className={twMerge(
policyDetails[policy.policyType].className,
"flex w-min items-center gap-1.5 whitespace-nowrap"
)}
>
<FontAwesomeIcon icon={policyDetails[policy.policyType].icon} />
<Badge variant={variant}>
<Icon />
<span>{policyDetails[policy.policyType].name}</span>
</Badge>
</Td>
@@ -196,7 +193,7 @@ export const ApprovalPolicyRow = ({
className={twMerge("grow border-mineshaft-600", index !== 0 && "border-r")}
/>
{labels.length > 1 && (
<Badge className="my-auto flex h-5 w-min min-w-5 items-center justify-center gap-1.5 bg-mineshaft-400/50 text-center whitespace-nowrap text-bunker-200">
<Badge variant="neutral">
<span>{index + 1}</span>
</Badge>
)}
@@ -225,8 +222,8 @@ export const ApprovalPolicyRow = ({
<span className="text-xs">
<Tooltip content="This user has been deactivated and no longer has an active organization membership.">
<div>
<Badge className="pointer-events-none mr-auto ml-1 flex h-5 w-min items-center gap-1.5 bg-mineshaft-400/50 whitespace-nowrap text-bunker-300">
<FontAwesomeIcon icon={faBan} />
<Badge variant="neutral">
<BanIcon />
Inactive
</Badge>
</div>
@@ -1,10 +1,10 @@
import { components, OptionProps } from "react-select";
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons";
import { faBan } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { BanIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { Badge } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { BypasserType } from "@app/hooks/api/accessApproval/types";
import { ApproverType } from "@app/hooks/api/secretApproval/types";
@@ -26,8 +26,8 @@ export const PolicyMemberOption = ({
{children}
</p>
{!props.data.isOrgMembershipActive && (
<Badge className="pointer-events-none mr-auto ml-1 flex h-5 w-min items-center gap-1.5 bg-mineshaft-400/50 whitespace-nowrap text-bunker-300">
<FontAwesomeIcon icon={faBan} />
<Badge className="ml-auto" variant="neutral">
<BanIcon />
Inactive
</Badge>
)}
@@ -1,23 +1,19 @@
import { useEffect, useState } from "react";
import {
faArrowUpRightFromSquare,
faBookOpen,
faCheck,
faCheckCircle,
faChevronDown,
faCodeBranch,
faCodeMerge,
faMagnifyingGlass,
faSearch,
faXmark
faSearch
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useSearch } from "@tanstack/react-router";
import { format, formatDistance } from "date-fns";
import { GitMergeIcon, XIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import {
Badge,
Button,
DropdownMenu,
DropdownMenuContent,
@@ -30,6 +26,7 @@ import {
Skeleton,
Tooltip
} from "@app/components/v2";
import { Badge, DocumentationLinkBadge } from "@app/components/v3";
import { ROUTE_PATHS } from "@app/const/routes";
import {
ProjectPermissionMemberActions,
@@ -139,22 +136,9 @@ export const SecretApprovalRequest = () => {
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
<div>
<div className="flex items-start gap-1">
<div className="flex items-center gap-x-2">
<p className="text-xl font-medium text-mineshaft-100">Change Requests</p>
<a
href="https://infisical.com/docs/documentation/platform/pr-workflows"
target="_blank"
rel="noopener noreferrer"
>
<div className="mt-[0.32rem] ml-1 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.5" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-[10px]"
/>
</div>
</a>
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/pr-workflows" />
</div>
<p className="text-sm text-bunker-300">Review pending and closed change requests</p>
</div>
@@ -329,12 +313,18 @@ export const SecretApprovalRequest = () => {
{status === "close" && (
<Tooltip content={updatedAt ? format(new Date(updatedAt), "M/dd/yyyy h:mm a") : ""}>
<div className="my-auto ml-auto">
<Badge
variant={hasMerged ? "success" : "danger"}
className="flex h-min items-center gap-1"
>
<FontAwesomeIcon icon={hasMerged ? faCodeMerge : faXmark} />
{hasMerged ? "Merged" : "Rejected"}
<Badge variant={hasMerged ? "success" : "danger"}>
{hasMerged ? (
<>
<GitMergeIcon />
Merged
</>
) : (
<>
<XIcon />
Rejected
</>
)}
</Badge>
</div>
</Tooltip>
@@ -33,7 +33,6 @@ import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { CreateSecretRotationV2Modal } from "@app/components/secret-rotations-v2";
import {
Badge,
Button,
DeleteActionModal,
DropdownMenu,
@@ -51,10 +50,12 @@ import {
ModalContent,
Tooltip
} from "@app/components/v2";
import { Badge } from "@app/components/v3";
import {
ProjectPermissionActions,
ProjectPermissionDynamicSecretActions,
ProjectPermissionSub,
useOrgPermission,
useProject,
useProjectPermission,
useSubscription
@@ -64,6 +65,7 @@ import {
ProjectPermissionSecretActions,
ProjectPermissionSecretRotationActions
} from "@app/context/ProjectPermissionContext/types";
import { OrgMembershipRole } from "@app/helpers/roles";
import { usePopUp } from "@app/hooks";
import {
useCreateFolder,
@@ -203,6 +205,8 @@ export const ActionBar = ({
const { permission } = useProjectPermission();
const { data: vaultConfigs = [] } = useGetVaultExternalMigrationConfigs();
const hasVaultConnection = vaultConfigs.some((config) => config.connectionId);
const { hasOrgRole } = useOrgPermission();
const isOrgAdmin = hasOrgRole(OrgMembershipRole.Admin);
const handleFolderCreate = async (folderName: string, description: string | null) => {
try {
@@ -825,7 +829,9 @@ export const ActionBar = ({
>
<div className="flex w-full justify-between">
<span>Tags</span>
{Boolean(filteredTags) && <Badge>{filteredTags} Applied</Badge>}
{Boolean(filteredTags) && (
<Badge variant="info">{filteredTags} Applied</Badge>
)}
</div>
</DropdownSubMenuTrigger>
<DropdownSubMenuContent className="max-h-80 thin-scrollbar overflow-y-auto rounded-l-none">
@@ -905,7 +911,9 @@ export const ActionBar = ({
return;
}
handlePopUpOpen("upgradePlan");
handlePopUpOpen("upgradePlan", {
featureName: "PIT Recovery"
});
}}
leftIcon={<FontAwesomeIcon icon={faCodeCommit} />}
isLoading={isSnapshotCountLoading}
@@ -1020,6 +1028,7 @@ export const ActionBar = ({
return;
}
handlePopUpOpen("upgradePlan", {
featureName: "Dynamic Secrets",
isEnterpriseFeature: true
});
}}
@@ -1048,7 +1057,9 @@ export const ActionBar = ({
handlePopUpClose("misc");
return;
}
handlePopUpOpen("upgradePlan");
handlePopUpOpen("upgradePlan", {
featureName: "Secret Rotation"
});
}}
variant="outline_bg"
className="h-10 text-left"
@@ -1116,25 +1127,33 @@ export const ActionBar = ({
})}
>
{(isAllowed) => (
<Button
leftIcon={
<img
src="/images/integrations/Vault.png"
alt="HashiCorp Vault"
className="h-4 w-4"
/>
<Tooltip
content={
!isOrgAdmin
? "Only organization admins can import secrets from HashiCorp Vault"
: undefined
}
onClick={() => {
handlePopUpOpen("importFromVault");
handlePopUpClose("misc");
}}
isDisabled={!isAllowed}
variant="outline_bg"
className="h-10 text-left"
isFullWidth
>
Add from HashiCorp Vault
</Button>
<Button
leftIcon={
<img
src="/images/integrations/Vault.png"
alt="HashiCorp Vault"
className="h-4 w-4"
/>
}
onClick={() => {
handlePopUpOpen("importFromVault");
handlePopUpClose("misc");
}}
isDisabled={!isAllowed || !isOrgAdmin}
variant="outline_bg"
className="h-10 text-left"
isFullWidth
>
Add from HashiCorp Vault
</Button>
</Tooltip>
)}
</ProjectPermissionCan>
)}
@@ -1214,7 +1233,11 @@ export const ActionBar = ({
environment={environment}
secretPath={secretPath}
projectId={projectId}
onUpgradePlan={() => handlePopUpOpen("upgradePlan")}
onUpgradePlan={() =>
handlePopUpOpen("upgradePlan", {
featureName: "Secret Imports"
})
}
isOpen={popUp.addSecretImport.isOpen}
onClose={() => handlePopUpClose("addSecretImport")}
onTogglePopUp={(isOpen) => handlePopUpToggle("addSecretImport", isOpen)}
@@ -1277,13 +1300,7 @@ export const ActionBar = ({
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature}
text={
subscription.slug === null
? "You can perform this action under an Enterprise license"
: `You can perform this action if you switch to Infisical's ${
popUp.upgradePlan.data.isEnterpriseFeature ? "Enterprise" : "Pro"
} plan`
}
text={`Your current plan does not include access to ${popUp.upgradePlan.data?.featureName}. To unlock this feature, please upgrade to Infisical ${popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro"} plan.`}
/>
)}
<Modal
@@ -36,9 +36,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -48,9 +47,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -95,9 +95,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -107,9 +106,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -37,9 +37,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -49,9 +48,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: z
.string()
@@ -75,8 +75,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -86,8 +86,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: slugSchema(),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -39,9 +39,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -51,9 +50,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: slugSchema(),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -240,8 +240,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -251,8 +251,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: slugSchema(),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -13,16 +13,12 @@ import {
} from "react-icons/si";
import { VscAzure } from "react-icons/vsc";
import { faAws, faGithub, faGoogle } from "@fortawesome/free-brands-svg-icons";
import {
faArrowUpRightFromSquare,
faBookOpen,
faClock,
faDatabase
} from "@fortawesome/free-solid-svg-icons";
import { faClock, faDatabase } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AnimatePresence, motion } from "framer-motion";
import { Modal, ModalContent } from "@app/components/v2";
import { DocumentationLinkBadge } from "@app/components/v3";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { ProjectEnv } from "@app/hooks/api/types";
@@ -202,23 +198,11 @@ export const CreateDynamicSecretForm = ({
<Modal isOpen={isOpen} onOpenChange={(state) => handleFormReset(state)}>
<ModalContent
title={
<div className="flex items-center">
<div className="flex items-center gap-x-2">
<span>{modalTitle ? `${modalTitle} Dynamic Secret` : "Dynamic Secrets"} </span>
<a
<DocumentationLinkBadge
href={`https://infisical.com/docs/documentation/platform/dynamic-secrets/${selectedProvider ? (UniqueLinks[selectedProvider] ?? selectedProvider) : "overview"}`}
target="_blank"
className="mb-0.5 ml-1.5"
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>
}
subTitle="Configure dynamic secret parameters"
@@ -234,11 +218,11 @@ export const CreateDynamicSecretForm = ({
exit={{ opacity: 0, translateX: -30 }}
>
<div className="mb-4 text-mineshaft-300">Select a service to connect to:</div>
<div className="flex flex-wrap items-center gap-4">
<div className="grid grid-cols-4 gap-2">
{DYNAMIC_SECRET_LIST.map(({ icon, provider, title }) => (
<div
key={`dynamic-secret-provider-${provider}`}
className="flex h-32 w-32 cursor-pointer flex-col items-center space-y-4 rounded-sm border border-mineshaft-500 bg-bunker-600 p-6 transition-all hover:border-primary/70 hover:bg-primary/10 hover:text-white"
className="flex h-30 w-42 cursor-pointer flex-col items-center space-y-4 rounded-sm border border-mineshaft-500 bg-bunker-600 p-6 transition-all hover:border-primary/70 hover:bg-primary/10 hover:text-white"
role="button"
tabIndex={0}
onClick={() => {
@@ -60,9 +60,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -72,9 +71,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: slugSchema(),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -1,5 +1,6 @@
import { useState } from "react";
import { Controller, FieldValues, useFieldArray, useForm } from "react-hook-form";
import { faQuestionCircle, faTrash } from "@fortawesome/free-solid-svg-icons";
import { faInfoCircle, faQuestionCircle, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
@@ -21,16 +22,22 @@ import {
TextArea,
Tooltip
} from "@app/components/v2";
import { useOrgPermission } from "@app/context";
import { OrgPermissionSubjects } from "@app/context/OrgPermissionContext";
import { OrgGatewayPermissionActions } from "@app/context/OrgPermissionContext/types";
import { OrgMembershipRole } from "@app/helpers/roles";
import { gatewaysQueryKeys, useCreateDynamicSecret } from "@app/hooks/api";
import {
DynamicSecretProviders,
KubernetesDynamicSecretCredentialType
} from "@app/hooks/api/dynamicSecret/types";
import { useGetVaultExternalMigrationConfigs } from "@app/hooks/api/migration/queries";
import { VaultKubernetesRole } from "@app/hooks/api/migration/types";
import { ProjectEnv } from "@app/hooks/api/types";
import { slugSchema } from "@app/lib/schemas";
import { VaultKubernetesImportModal } from "./VaultKubernetesImportModal";
enum RoleType {
ClusterRole = "cluster-role",
Role = "role"
@@ -99,8 +106,8 @@ const formSchema = z
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -110,8 +117,8 @@ const formSchema = z
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: slugSchema(),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -162,11 +169,14 @@ export const KubernetesInputForm = ({
environments,
isSingleEnvironmentMode
}: Props) => {
const [isVaultImportModalOpen, setIsVaultImportModalOpen] = useState(false);
const {
control,
formState: { isSubmitting },
handleSubmit,
watch
watch,
setValue
} = useForm<TForm>({
resolver: zodResolver(formSchema),
defaultValues: {
@@ -186,18 +196,88 @@ export const KubernetesInputForm = ({
}
});
const { fields, append, remove } = useFieldArray({
const { fields, append, remove, replace } = useFieldArray({
control,
name: "provider.audiences"
});
const createDynamicSecret = useCreateDynamicSecret();
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
const { data: vaultConfigs = [] } = useGetVaultExternalMigrationConfigs();
const hasVaultConnection = vaultConfigs.some((config) => config.connectionId);
const { hasOrgRole } = useOrgPermission();
const isOrgAdmin = hasOrgRole(OrgMembershipRole.Admin);
const sslEnabled = watch("provider.sslEnabled");
const credentialType = watch("provider.credentialType");
const authMethod = watch("provider.authMethod");
const handleVaultImport = (role: VaultKubernetesRole) => {
try {
setValue("name", role.name);
setValue("provider.url", role.config.kubernetes_host);
// Set CA certificate if available
if (role.config.kubernetes_ca_cert) {
setValue("provider.ca", role.config.kubernetes_ca_cert);
setValue("provider.sslEnabled", true);
}
// Determine credential type based on role configuration
if (role.service_account_name) {
// Static credential type
setValue("provider.credentialType", KubernetesDynamicSecretCredentialType.Static);
setValue("provider.serviceAccountName", role.service_account_name);
// Set namespace (single namespace for static)
if (role.allowed_kubernetes_namespaces && role.allowed_kubernetes_namespaces.length > 0) {
setValue("provider.namespace", role.allowed_kubernetes_namespaces[0]);
}
} else if (role.kubernetes_role_name) {
// Dynamic credential type
setValue("provider.credentialType", KubernetesDynamicSecretCredentialType.Dynamic);
setValue("provider.role", role.kubernetes_role_name);
// Set role type
const roleType =
role.kubernetes_role_type === "ClusterRole" ? RoleType.ClusterRole : RoleType.Role;
setValue("provider.roleType", roleType);
// Set allowed namespaces (comma-separated for dynamic)
if (role.allowed_kubernetes_namespaces && role.allowed_kubernetes_namespaces.length > 0) {
setValue("provider.namespace", role.allowed_kubernetes_namespaces.join(", "));
}
}
// Set TTLs if available
if (role.token_default_ttl) {
const defaultTTL = `${role.token_default_ttl}s`;
setValue("defaultTTL", defaultTTL);
}
if (role.token_max_ttl) {
const maxTTL = `${role.token_max_ttl}s`;
setValue("maxTTL", maxTTL);
}
// Set audiences if available
if (role.token_default_audiences && role.token_default_audiences.length > 0) {
replace(role.token_default_audiences);
}
createNotification({
type: "info",
text: "Configuration loaded successfully from HashiCorp Vault"
});
} catch {
createNotification({
type: "error",
text: "Failed to load configuration from HashiCorp Vault"
});
}
};
const handleCreateDynamicSecret = async (formData: TForm) => {
const { provider, usernameTemplate, ...rest } = formData;
// wait till previous request is finished
@@ -235,6 +315,39 @@ export const KubernetesInputForm = ({
return (
<form onSubmit={handleSubmit(handleCreateDynamicSecret)} autoComplete="off">
<div>
{hasVaultConnection && (
<div className="mb-4 flex items-center justify-between rounded-md border border-primary-400/30 bg-primary/10 px-3 py-2.5">
<div className="flex items-center gap-2 text-sm">
<FontAwesomeIcon icon={faInfoCircle} className="text-primary" />
<span className="text-mineshaft-200">Load values from HashiCorp Vault</span>
</div>
<Tooltip
content={
!isOrgAdmin
? "Only organization admins can import configurations from HashiCorp Vault"
: undefined
}
>
<Button
variant="outline_bg"
size="xs"
type="button"
onClick={() => setIsVaultImportModalOpen(true)}
isDisabled={!isOrgAdmin}
leftIcon={
<img
src="/images/integrations/Vault.png"
alt="HashiCorp Vault"
className="h-4 w-4"
/>
}
>
Load from Vault
</Button>
</Tooltip>
</div>
)}
<div className="flex items-center space-x-2">
<div className="grow">
<Controller
@@ -286,8 +399,8 @@ export const KubernetesInputForm = ({
</div>
</div>
<div>
<div className="mt-4 mb-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
Configuration
<div className="mt-4 mb-4 border-b border-mineshaft-500 pb-2 pl-1">
<h3 className="font-medium text-mineshaft-200">Configuration</h3>
</div>
<div className="flex flex-col">
<div className="flex items-center space-x-2">
@@ -654,6 +767,11 @@ export const KubernetesInputForm = ({
Cancel
</Button>
</div>
<VaultKubernetesImportModal
isOpen={isVaultImportModalOpen}
onOpenChange={setIsVaultImportModalOpen}
onImport={handleVaultImport}
/>
</form>
);
};
@@ -61,9 +61,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -73,9 +72,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: slugSchema(),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -50,9 +50,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -62,9 +61,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: slugSchema(),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -40,9 +40,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -52,9 +51,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: slugSchema(),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -44,9 +44,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -56,9 +55,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: slugSchema(),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -37,9 +37,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -49,9 +48,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: slugSchema(),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -35,9 +35,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -47,9 +46,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: slugSchema(),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -37,9 +37,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -49,9 +48,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: slugSchema(),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -35,9 +35,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -47,9 +46,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: slugSchema(),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -75,9 +75,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -87,9 +86,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: slugSchema(),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -0,0 +1,223 @@
import { useEffect, useState } from "react";
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import {
Button,
FilterableSelect,
FormControl,
Modal,
ModalClose,
ModalContent
} from "@app/components/v2";
import {
useGetVaultKubernetesRoles,
useGetVaultMounts,
useGetVaultNamespaces
} from "@app/hooks/api/migration/queries";
import { VaultKubernetesRole } from "@app/hooks/api/migration/types";
type Props = {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
onImport: (role: VaultKubernetesRole) => void;
};
type ContentProps = {
onClose: () => void;
onImport: (role: VaultKubernetesRole) => void;
};
const Content = ({ onClose, onImport }: ContentProps) => {
const [selectedNamespace, setSelectedNamespace] = useState<string | null>(null);
const [selectedMountPath, setSelectedMountPath] = useState<string | null>(null);
const [selectedRole, setSelectedRole] = useState<VaultKubernetesRole | null>(null);
const [shouldFetchRoles, setShouldFetchRoles] = useState(false);
const [shouldFetchMounts, setShouldFetchMounts] = useState(false);
const { data: namespaces, isLoading: isLoadingNamespaces } = useGetVaultNamespaces();
const { data: roles, isLoading: isLoadingRoles } = useGetVaultKubernetesRoles(
shouldFetchRoles,
selectedNamespace ?? undefined,
selectedMountPath ?? undefined
);
const { data: mounts, isLoading: isLoadingMounts } = useGetVaultMounts(
shouldFetchMounts,
selectedNamespace ?? undefined
);
// Filter to only show Kubernetes mounts
const kubernetesMounts = mounts?.filter((mount) => mount.type === "kubernetes");
// Enable fetching mounts when namespace is selected
useEffect(() => {
if (selectedNamespace) {
setShouldFetchMounts(true);
}
}, [selectedNamespace]);
// Enable fetching roles when both namespace and mount path are selected
useEffect(() => {
if (selectedNamespace && selectedMountPath) {
setShouldFetchRoles(true);
} else {
setShouldFetchRoles(false);
}
}, [selectedNamespace, selectedMountPath]);
const handleImport = () => {
if (!selectedRole) {
createNotification({
type: "error",
text: "Please select a Vault Kubernetes role to load"
});
return;
}
if (!selectedNamespace) {
createNotification({ type: "error", text: "Please select a namespace" });
return;
}
if (!mounts || mounts.length === 0) {
createNotification({
type: "error",
text: "No Vault mounts found. Please ensure you have Kubernetes secrets engine configured."
});
return;
}
onImport(selectedRole);
onClose();
};
return (
<>
<div className="mb-4 rounded-md bg-primary/10 p-3 text-sm text-mineshaft-200">
<div className="flex items-start gap-2">
<FontAwesomeIcon icon={faInfoCircle} className="mt-0.5 text-primary" />
<div className="space-y-1.5 text-xs leading-relaxed">
<p>
Select a Kubernetes secrets engine role from Vault to pre-fill the form with its
configuration including cluster URL, CA certificate, TTL settings, etc.
</p>
</div>
</div>
</div>
<FormControl
label="Namespace"
className="mb-4"
tooltipText="Select the Vault namespace containing the Kubernetes secrets engine."
>
<>
<FilterableSelect
value={namespaces?.find((ns) => ns.name === selectedNamespace)}
onChange={(value) => {
if (value && !Array.isArray(value)) {
const namespace = value as { id: string; name: string };
setSelectedNamespace(namespace.name);
setSelectedMountPath(null);
setSelectedRole(null);
}
}}
options={namespaces || []}
getOptionValue={(option) => option.name}
getOptionLabel={(option) => (option.name === "/" ? "root" : option.name)}
isDisabled={isLoadingNamespaces}
placeholder="Select namespace..."
className="w-full"
/>
<p className="mt-1 text-xs text-mineshaft-400">
Select the Vault namespace to fetch available Kubernetes secrets engines
</p>
</>
</FormControl>
<FormControl
label="Kubernetes Secrets Engine"
className="mb-4"
tooltipText="Select the Kubernetes secrets engine mount to fetch available roles."
>
<>
<FilterableSelect
value={kubernetesMounts?.find((mount) => mount.path === selectedMountPath)}
onChange={(value) => {
if (value && !Array.isArray(value)) {
const mount = value as { path: string; type: string; version: string | null };
setSelectedMountPath(mount.path.replace(/\/$/, "")); // Remove trailing slash
setSelectedRole(null);
}
}}
options={kubernetesMounts || []}
getOptionValue={(option) => option.path}
getOptionLabel={(option) => option.path.replace(/\/$/, "")}
isDisabled={isLoadingMounts || !kubernetesMounts?.length}
placeholder="Select Kubernetes secrets engine..."
className="w-full"
/>
<p className="mt-1 text-xs text-mineshaft-400">
Choose a Kubernetes secrets engine mount to list available roles
</p>
</>
</FormControl>
<FormControl label="Kubernetes Role" className="mb-6">
<>
<FilterableSelect
value={selectedRole}
onChange={(value) => {
if (value && !Array.isArray(value)) {
setSelectedRole(value as VaultKubernetesRole);
} else {
setSelectedRole(null);
}
}}
options={roles || []}
getOptionValue={(option) => option.name}
getOptionLabel={(option) => option.name}
isDisabled={isLoadingRoles || !roles?.length || !selectedMountPath}
placeholder={
!selectedMountPath ? "Select a mount path first..." : "Select a role to load..."
}
isClearable
className="w-full"
/>
<p className="mt-1 text-xs text-mineshaft-400">
Choose a Kubernetes role from the selected mount to load its configuration
</p>
</>
</FormControl>
<div className="mt-8 flex space-x-4">
<Button
onClick={handleImport}
isDisabled={!selectedRole || isLoadingMounts || isLoadingRoles}
>
Load Configuration
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
</div>
</>
);
};
export const VaultKubernetesImportModal = ({ isOpen, onOpenChange, onImport }: Props) => {
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent
bodyClassName="overflow-visible"
title="Load from HashiCorp Vault"
subTitle="Select a Kubernetes secrets engine role to load its configuration."
className="max-w-2xl"
>
<Content onClose={() => onOpenChange(false)} onImport={onImport} />
</ModalContent>
</Modal>
);
};
@@ -66,8 +66,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -77,8 +77,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() }),
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { faArrowUpRightFromSquare, faBookOpen, faClone } from "@fortawesome/free-solid-svg-icons";
import { faClone } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
@@ -14,6 +14,7 @@ import {
Switch
} from "@app/components/v2";
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
import { DocumentationLinkBadge } from "@app/components/v3";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import { useDebounce } from "@app/hooks";
import { useGetAccessibleSecrets } from "@app/hooks/api/dashboard";
@@ -227,23 +228,9 @@ export const ReplicateFolderFromBoard = ({
bodyClassName="overflow-visible"
className="max-w-2xl"
title={
<div className="flex items-center gap-1 text-mineshaft-300">
<div className="flex items-center gap-x-2 text-mineshaft-300">
<span>Replicate Secrets</span>
<a
target="_blank"
href="https://infisical.com/docs/documentation/platform/folder#replicating-folder-contents"
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/folder#replicating-folder-contents" />
</div>
}
subTitle="Copy folder contents from other locations into this context"
@@ -1,16 +1,12 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
import React, { useCallback, useState } from "react";
import {
faClipboardCheck,
faCodeCommit,
faFolder,
faKey,
faSave
} from "@fortawesome/free-solid-svg-icons";
import { faCodeCommit, faFolder, faKey, faSave } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AnimatePresence, motion } from "framer-motion";
import { ClipboardCheckIcon } from "lucide-react";
import { Badge, Button, Input, Modal, ModalContent } from "@app/components/v2";
import { Button, Input, Modal, ModalContent } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { PendingAction } from "@app/hooks/api/secretFolders/types";
import { SecretVersionDiffView } from "@app/pages/secret-manager/CommitDetailsPage/components/SecretVersionDiffView";
import { HIDDEN_SECRET_VALUE_API_MASK } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem";
@@ -329,9 +325,9 @@ export const CommitForm: React.FC<CommitFormProps> = ({
<div className="flex-1">
{/* Header */}
<div className="flex items-center gap-2">
<div className="h-2 w-2 rounded-full bg-yellow-500" />
<div className="h-2 w-2 rounded-full bg-warning" />
<span className="font-medium text-mineshaft-100">Pending Changes</span>
<Badge variant="primary" className="text-xs">
<Badge variant="warning">
{totalChangesCount} Change{totalChangesCount !== 1 ? "s" : ""}
</Badge>
</div>
@@ -375,9 +371,9 @@ export const CommitForm: React.FC<CommitFormProps> = ({
<ModalContent
title={
<div className="flex items-center gap-2">
<FontAwesomeIcon icon={faClipboardCheck} className="text-mineshaft-400" />
Review Changes
<Badge variant="primary" className="mt-[0.05rem]">
<Badge variant="warning">
<ClipboardCheckIcon />
{totalChangesCount} Change{totalChangesCount !== 1 ? "s" : ""}
</Badge>
</div>
@@ -9,14 +9,13 @@ import {
faFolder,
faKey,
faRotate,
faSearch,
faWarning
faSearch
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AlertTriangleIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import {
Badge,
Button,
DropdownMenu,
DropdownMenuContent,
@@ -38,6 +37,7 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { useProject } from "@app/context";
import {
getUserTablePreference,
@@ -396,11 +396,8 @@ export const CompareEnvironments = ({ secretPath }: Props) => {
</>
}
>
<Badge
variant="primary"
className="-mt-[0.05rem] flex h-4 items-center gap-x-1 pt-[0.1rem] leading-3 font-normal"
>
<FontAwesomeIcon icon={faWarning} className="-mt-[0.1rem] w-2.5" />
<Badge variant="warning">
<AlertTriangleIcon />
{missingKeyCount}
</Badge>
</Tooltip>
@@ -37,9 +37,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -49,9 +48,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
})
.nullable(),
newName: slugSchema().optional(),
@@ -71,9 +71,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -83,9 +82,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
})
.nullable(),
newName: slugSchema().optional(),
@@ -22,9 +22,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -34,9 +33,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
newName: slugSchema().optional()
});
@@ -73,8 +73,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -84,8 +84,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
newName: slugSchema().optional(),
metadata: z
@@ -39,9 +39,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -51,9 +50,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
})
.nullable(),
newName: slugSchema().optional(),
@@ -241,8 +241,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -252,8 +252,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
})
.nullable(),
newName: slugSchema().optional(),
@@ -58,9 +58,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -70,9 +69,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
newName: slugSchema().optional(),
usernameTemplate: z.string().trim().nullable().optional()
@@ -97,8 +97,8 @@ const formSchema = z
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -108,8 +108,8 @@ const formSchema = z
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
newName: slugSchema().optional(),
usernameTemplate: z.string().trim().optional()
@@ -51,9 +51,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -63,9 +62,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
})
.nullable(),
newName: slugSchema().optional(),
@@ -50,9 +50,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -62,9 +61,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
})
.nullable(),
newName: slugSchema().optional(),
@@ -33,9 +33,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -45,9 +44,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
})
.nullable(),
newName: slugSchema().optional(),
@@ -35,9 +35,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -47,9 +46,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
newName: slugSchema().optional(),
usernameTemplate: z.string().trim().nullable().optional()
@@ -38,9 +38,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -50,9 +49,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
})
.nullable(),
newName: slugSchema().optional(),
@@ -37,9 +37,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
usernameTemplate: z.string().trim().nullable().optional(),
maxTTL: z
@@ -50,9 +49,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
newName: slugSchema().optional()
});
@@ -37,9 +37,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -49,9 +48,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
newName: slugSchema().optional(),
usernameTemplate: z.string().trim().nullable().optional()
@@ -35,9 +35,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -47,9 +46,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
newName: slugSchema().optional(),
usernameTemplate: z.string().trim().nullable().optional()
@@ -73,9 +73,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
maxTTL: z
.string()
@@ -85,9 +84,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
// a day
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
})
.nullable(),
newName: slugSchema().optional(),
@@ -64,8 +64,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
usernameTemplate: z.string().trim().nullable().optional(),
maxTTL: z
@@ -76,8 +76,8 @@ const formSchema = z.object({
const valMs = ms(val);
if (valMs < 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
if (valMs > ms("10y"))
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than 10 years" });
}),
newName: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
});
@@ -226,7 +226,7 @@ export const EnvironmentTabs = ({ secretPath }: Props) => {
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can add custom environments if you switch to Infisical's Pro plan."
text="Your current plan does not include access to adding custom environments. To unlock this feature, please upgrade to Infisical Pro plan."
/>
<AddEnvironmentModal
isOpen={popUp.createEnvironment.isOpen}
@@ -1,15 +1,9 @@
import { ChangeEvent, Dispatch, DragEvent, SetStateAction, useState } from "react";
import { useTranslation } from "react-i18next";
import { subject } from "@casl/ability";
import {
faArrowRight,
faAsterisk,
faComment,
faKey,
faPlus,
faUpload
} from "@fortawesome/free-solid-svg-icons";
import { faArrowRight, faPlus, faUpload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AsteriskIcon, KeyIcon, MessageSquareIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
@@ -22,7 +16,6 @@ import {
parseYaml
} from "@app/components/utilities/parseSecrets";
import {
Badge,
Button,
FormLabel,
Lottie,
@@ -31,6 +24,7 @@ import {
Select,
SelectItem
} from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { usePopUp, useToggle } from "@app/hooks";
import { PendingAction } from "@app/hooks/api/secretFolders/types";
@@ -115,23 +109,23 @@ const MatrixImportModalTableRow = ({
</td>
<td className="whitespace-nowrap">
<div className="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">
<Badge isFullWidth variant="neutral">
{mapKey === "key" && (
<>
<FontAwesomeIcon icon={faKey} />
<span>Secret Key</span>
<KeyIcon />
Secret Key
</>
)}
{mapKey === "value" && (
<>
<FontAwesomeIcon icon={faAsterisk} />
<span>Secret Value</span>
<AsteriskIcon />
Secret Value
</>
)}
{mapKey === "comment" && (
<>
<FontAwesomeIcon icon={faComment} />
<span>Comment</span>
<MessageSquareIcon />
Comment
</>
)}
</Badge>
@@ -321,7 +321,7 @@ export const SecretDetailSidebar = ({
onOpenChange={(isUpgradeModalOpen) =>
handlePopUpToggle("secretAccessUpgradePlan", isUpgradeModalOpen)
}
text="Secret access analysis is only available on Infisical's Pro plan and above."
text="Secret access analysis feature can be unlocked if you upgrade to Infisical Pro plan."
/>
<Modal
isOpen={popUp.secretReferenceTree.isOpen}
@@ -67,10 +67,10 @@ export const SecretRotationItem = ({
</Tooltip>
)}
</div>
<SecretRotationV2StatusBadge className="mx-2" secretRotation={secretRotation} />
<SecretRotationV2StatusBadge secretRotation={secretRotation} />
<div
key="actions"
className="flex h-full shrink-0 self-start transition-all group-hover:gap-x-2"
className="ml-2 flex h-full shrink-0 self-start transition-all group-hover:gap-x-2"
>
<Tooltip content="View Rotation Secrets">
<IconButton
@@ -397,7 +397,7 @@ const Page = () => {
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can add secret rotation if you switch to Infisical's Pro plan."
text="Adding secret rotations can be unlocked if you upgrade to Infisical Pro plan."
/>
<Modal
isOpen={popUp.secretRotationV2.isOpen}
@@ -1,7 +1,6 @@
import { useCallback } from "react";
import { subject } from "@casl/ability";
import {
faBan,
faCheck,
faCopy,
faDownload,
@@ -15,6 +14,7 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import { BanIcon, RefreshCwIcon } from "lucide-react";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
@@ -26,7 +26,6 @@ import {
SecretSyncRemoveStatusBadge
} from "@app/components/secret-syncs";
import {
Badge,
Button,
DropdownMenu,
DropdownMenuContent,
@@ -35,6 +34,7 @@ import {
IconButton,
Tooltip
} from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { ROUTE_PATHS } from "@app/const/routes";
import { ProjectPermissionSub } from "@app/context";
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
@@ -143,24 +143,19 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => {
<SecretSyncImportStatusBadge secretSync={secretSync} />
<SecretSyncRemoveStatusBadge secretSync={secretSync} />
{secretSync.isAutoSyncEnabled ? (
<Badge
variant="success"
className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap"
>
<FontAwesomeIcon icon={faRotate} />
<span>Auto-Sync Enabled</span>
<Badge variant="info">
<RefreshCwIcon />
Auto-Sync Enabled
</Badge>
) : (
<Tooltip
className="text-xs"
content="Auto-Sync is disabled. Changes to the source location will not be automatically synced to the destination."
>
<div>
<Badge className="flex h-5 w-min items-center gap-1.5 bg-mineshaft-400/50 whitespace-nowrap text-bunker-300">
<FontAwesomeIcon icon={faBan} />
<span>Auto-Sync Disabled</span>
</Badge>
</div>
<Badge variant="neutral">
<BanIcon />
Auto-Sync Disabled
</Badge>
</Tooltip>
)}
<div>
@@ -1,5 +1,5 @@
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { Badge } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { AWS_REGIONS } from "@app/helpers/appConnections";
import { TAwsParameterStoreSync } from "@app/hooks/api/secretSyncs/types/aws-parameter-store-sync";
@@ -18,8 +18,8 @@ export const AwsParameterStoreSyncDestinationSection = ({ secretSync }: Props) =
<>
<GenericFieldLabel label="Region">
{awsRegion?.name}
<Badge className="ml-1" variant="success">
{awsRegion?.slug}{" "}
<Badge className="ml-1" variant="neutral">
{awsRegion?.slug}
</Badge>
</GenericFieldLabel>
<GenericFieldLabel label="Path">{path}</GenericFieldLabel>
@@ -1,5 +1,5 @@
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { Badge } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { AWS_REGIONS } from "@app/helpers/appConnections";
import {
AwsSecretsManagerSyncMappingBehavior,
@@ -19,8 +19,8 @@ export const AwsSecretsManagerSyncDestinationSection = ({ secretSync }: Props) =
<>
<GenericFieldLabel label="Region">
{awsRegion?.name}
<Badge className="ml-1" variant="success">
{awsRegion?.slug}{" "}
<Badge className="ml-1" variant="neutral">
{awsRegion?.slug}
</Badge>
</GenericFieldLabel>
<GenericFieldLabel label="Mapping Behavior" className="capitalize">
@@ -0,0 +1,19 @@
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { TChefSync } from "@app/hooks/api/secretSyncs/types/chef-sync";
type Props = {
secretSync: TChefSync;
};
export const ChefSyncDestinationSection = ({ secretSync }: Props) => {
const { destinationConfig } = secretSync;
return (
<>
<GenericFieldLabel label="Data Bag">{destinationConfig.dataBagName}</GenericFieldLabel>
<GenericFieldLabel label="Data Bag Item">
{destinationConfig.dataBagItemName}
</GenericFieldLabel>
</>
);
};
@@ -0,0 +1,21 @@
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { TNorthflankSync } from "@app/hooks/api/secretSyncs/types/northflank-sync";
type Props = {
secretSync: TNorthflankSync;
};
export const NorthflankSyncDestinationSection = ({ secretSync }: Props) => {
const { destinationConfig } = secretSync;
return (
<>
<GenericFieldLabel label="Project">
{destinationConfig.projectName || destinationConfig.projectId}
</GenericFieldLabel>
<GenericFieldLabel label="Secret Group">
{destinationConfig.secretGroupName || destinationConfig.secretGroupId}
</GenericFieldLabel>
</>
);
};
@@ -20,6 +20,7 @@ import { AzureKeyVaultSyncDestinationSection } from "./AzureKeyVaultSyncDestinat
import { BitbucketSyncDestinationSection } from "./BitbucketSyncDestinationSection";
import { CamundaSyncDestinationSection } from "./CamundaSyncDestinationSection";
import { ChecklySyncDestinationSection } from "./ChecklySyncDestinationSection";
import { ChefSyncDestinationSection } from "./ChefSyncDestinationSection";
import { CloudflarePagesSyncDestinationSection } from "./CloudflarePagesSyncDestinationSection";
import { CloudflareWorkersSyncDestinationSection } from "./CloudflareWorkersSyncDestinationSection";
import { DatabricksSyncDestinationSection } from "./DatabricksSyncDestinationSection";
@@ -33,6 +34,7 @@ import { HerokuSyncDestinationSection } from "./HerokuSyncDestinationSection";
import { HumanitecSyncDestinationSection } from "./HumanitecSyncDestinationSection";
import { LaravelForgeSyncDestinationSection } from "./LaravelForgeSyncDestinationSection";
import { NetlifySyncDestinationSection } from "./NetlifySyncDestinationSection";
import { NorthflankSyncDestinationSection } from "./NorthflankSyncDestinationSection";
import { OCIVaultSyncDestinationSection } from "./OCIVaultSyncDestinationSection";
import { RailwaySyncDestinationSection } from "./RailwaySyncDestinationSection";
import { RenderSyncDestinationSection } from "./RenderSyncDestinationSection";
@@ -146,12 +148,18 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }:
case SecretSync.Netlify:
DestinationComponents = <NetlifySyncDestinationSection secretSync={secretSync} />;
break;
case SecretSync.Northflank:
DestinationComponents = <NorthflankSyncDestinationSection secretSync={secretSync} />;
break;
case SecretSync.Bitbucket:
DestinationComponents = <BitbucketSyncDestinationSection secretSync={secretSync} />;
break;
case SecretSync.LaravelForge:
DestinationComponents = <LaravelForgeSyncDestinationSection secretSync={secretSync} />;
break;
case SecretSync.Chef:
DestinationComponents = <ChefSyncDestinationSection secretSync={secretSync} />;
break;
default:
throw new Error(`Unhandled Destination Section components: ${destination}`);
}
@@ -1,8 +1,8 @@
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 { 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 { TAwsParameterStoreSync } from "@app/hooks/api/secretSyncs/types/aws-parameter-store-sync";
type Props = {
@@ -40,11 +40,9 @@ export const AwsParameterStoreSyncOptionsSection = ({ secretSync }: Props) => {
}
>
<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,8 +1,10 @@
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 { 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 { TAwsSecretsManagerSync } from "@app/hooks/api/secretSyncs/types/aws-secrets-manager-sync";
type Props = {
@@ -40,11 +42,10 @@ export const AwsSecretsManagerSyncOptionsSection = ({ secretSync }: Props) => {
}
>
<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">
<Badge variant="neutral">
<EyeIcon />
<FontAwesomeIcon icon={faEye} />
<span>
{tags.length} Tag{tags.length > 1 ? "s" : ""}
</span>
{tags.length} Tag{tags.length > 1 ? "s" : ""}
</Badge>
</div>
</Tooltip>
@@ -1,5 +1,5 @@
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { Badge } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync";
type Props = {
@@ -5,7 +5,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ProjectPermissionCan } from "@app/components/permissions";
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { Badge, IconButton } from "@app/components/v2";
import { IconButton } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { ProjectPermissionSub } from "@app/context";
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP } from "@app/helpers/secretSyncs";
@@ -70,8 +71,10 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
case SecretSync.Checkly:
case SecretSync.DigitalOceanAppPlatform:
case SecretSync.Netlify:
case SecretSync.Northflank:
case SecretSync.Bitbucket:
case SecretSync.LaravelForge:
case SecretSync.Chef:
AdditionalSyncOptionsComponent = null;
break;
default:
@@ -114,7 +117,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
{AdditionalSyncOptionsComponent}
{disableSecretDeletion && (
<GenericFieldLabel label="Secret Deletion">
<Badge variant="primary">Disabled</Badge>
<Badge variant="neutral">Disabled</Badge>
</GenericFieldLabel>
)}
</div>
@@ -1,10 +1,12 @@
import { subject } from "@casl/ability";
import { faEdit, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
import { faEdit } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AlertTriangleIcon } from "lucide-react";
import { ProjectPermissionCan } from "@app/components/permissions";
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { Badge, IconButton, Tooltip } from "@app/components/v2";
import { IconButton, Tooltip } from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { ProjectPermissionSub } from "@app/context";
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
import { TSecretSync } from "@app/hooks/api/secretSyncs";
@@ -34,12 +36,9 @@ export const SecretSyncSourceSection = ({ secretSync, onEditSource }: Props) =>
{(!folder || !environment) && (
<Tooltip content="The source location for this sync has been deleted. Configure a new source or remove this sync.">
<div className="mr-1 inline-block w-min">
<Badge
className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap"
variant="primary"
>
<FontAwesomeIcon icon={faTriangleExclamation} />
<span>Folder Deleted</span>
<Badge variant="danger">
<AlertTriangleIcon />
Folder Deleted
</Badge>
</div>
</Tooltip>
@@ -123,7 +123,7 @@ export const EnvironmentSection = () => {
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can add custom environments if you switch to Infisical's Pro plan."
text="You have reached the maximum number of environments allowed on the free plan. Upgrade to Infisical Pro plan to add more environments."
/>
</div>
);
@@ -2,11 +2,11 @@
import { BsMicrosoftTeams } from "react-icons/bs";
import { faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { BanIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Badge,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
@@ -15,6 +15,7 @@ import {
Td,
Tr
} from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { useGetMicrosoftTeamsIntegrationTeams } from "@app/hooks/api";
import {
@@ -66,30 +67,36 @@ export const MicrosoftTeamsConfigRow = ({
{microsoftTeamsConfig.isAccessRequestNotificationEnabled &&
!isLoadingConfig &&
microsoftTeamsConfig.accessRequestChannels?.channelIds?.length > 0 ? (
<Badge>
<p>
{microsoftTeamsConfig.accessRequestChannels.channelIds
.map((channel) => microsoftTeamsChannelIdToName[channel])
.join(", ")}
</Badge>
</p>
) : isLoadingConfig ? (
<Spinner size="xs" />
) : (
<Badge variant="danger">Disabled</Badge>
<Badge variant="neutral">
<BanIcon />
Disabled
</Badge>
)}
</Td>
<Td>
{microsoftTeamsConfig.isSecretRequestNotificationEnabled &&
!isLoadingConfig &&
microsoftTeamsConfig.secretRequestChannels?.channelIds?.length > 0 ? (
<Badge>
<p>
{microsoftTeamsConfig.secretRequestChannels.channelIds
.map((channel) => microsoftTeamsChannelIdToName[channel])
.join(", ")}
</Badge>
</p>
) : isLoadingConfig ? (
<Spinner size="xs" />
) : (
<Badge variant="danger">Disabled</Badge>
<Badge variant="neutral">
<BanIcon />
Disabled
</Badge>
)}
</Td>
<Td>
@@ -2,11 +2,11 @@
import { BsSlack } from "react-icons/bs";
import { faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { BanIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Badge,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
@@ -15,6 +15,7 @@ import {
Td,
Tr
} from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { useGetSlackIntegrationChannels } from "@app/hooks/api";
import {
@@ -61,32 +62,38 @@ export const SlackConfigRow = ({ handlePopUpOpen, isSlackConfigLoading, slackCon
{slackConfig.isAccessRequestNotificationEnabled &&
!isLoadingConfig &&
slackConfig.accessRequestChannels.length > 0 ? (
<Badge>
<p>
{slackConfig.accessRequestChannels
.split(", ")
.map((channel) => slackChannelIdToName[channel])
.join(", ")}
</Badge>
</p>
) : isLoadingConfig ? (
<Spinner size="xs" />
) : (
<Badge variant="danger">Disabled</Badge>
<Badge variant="neutral">
<BanIcon />
Disabled
</Badge>
)}
</Td>
<Td>
{slackConfig.isSecretRequestNotificationEnabled &&
!isLoadingConfig &&
slackConfig.secretRequestChannels.length > 0 ? (
<Badge>
<p>
{slackConfig.secretRequestChannels
.split(", ")
.map((channel) => slackChannelIdToName[channel])
.join(", ")}
</Badge>
</p>
) : isLoadingConfig ? (
<Spinner size="xs" />
) : (
<Badge variant="danger">Disabled</Badge>
<Badge variant="neutral">
<BanIcon />
Disabled
</Badge>
)}
</Td>
<Td>
@@ -11,7 +11,6 @@ import { useNavigate, useSearch } from "@tanstack/react-router";
import { motion } from "framer-motion";
import {
Badge,
Button,
Card,
CardTitle,
@@ -25,6 +24,7 @@ import {
TabPanel,
Tabs
} from "@app/components/v2";
import { Badge } from "@app/components/v3";
import { ROUTE_PATHS } from "@app/const/routes";
import { useProject } from "@app/context";
import { useCreateIntegration } from "@app/hooks/api";
@@ -254,7 +254,7 @@ export const AWSParameterStoreConfigurePage = () => {
>
{awsRegions.map((awsRegion) => (
<SelectItem value={awsRegion.slug} key={`aws-environment-${awsRegion.slug}`}>
{awsRegion.name} <Badge variant="success">{awsRegion.slug}</Badge>
{awsRegion.name} <Badge variant="neutral">{awsRegion.slug}</Badge>
</SelectItem>
))}
</Select>
@@ -14,7 +14,6 @@ import { motion } from "framer-motion";
import z from "zod";
import {
Badge,
Button,
Card,
CardTitle,
@@ -29,6 +28,7 @@ import {
Tabs
} from "@app/components/v2";
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
import { Badge } from "@app/components/v3";
import { ROUTE_PATHS } from "@app/const/routes";
import { useProject } from "@app/context";
import { useCreateIntegration } from "@app/hooks/api";
@@ -332,7 +332,7 @@ export const AwsSecretManagerConfigurePage = () => {
className="flex w-full justify-between"
key={`aws-environment-${awsRegion.slug}`}
>
{awsRegion.name} <Badge variant="success">{awsRegion.slug}</Badge>
{awsRegion.name} <Badge variant="neutral">{awsRegion.slug}</Badge>
</SelectItem>
))}
</Select>