Merge branch 'main' into feat/point-in-time-revamp

This commit is contained in:
carlosmonastyrski
2025-05-20 10:37:41 -03:00
parent c01a98ccf1
commit 1748052cb0
731 changed files with 29323 additions and 10098 deletions
@@ -5,6 +5,7 @@ import { DropdownMenuItem } from "@radix-ui/react-dropdown-menu";
import { useSearch } from "@tanstack/react-router";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
DeleteActionModal,
DropdownMenu,
@@ -14,6 +15,10 @@ import {
Spinner
} from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
import {
ProjectPermissionCommitsActions,
ProjectPermissionSub
} from "@app/context/ProjectPermissionContext/types";
import { usePopUp } from "@app/hooks";
import { CommitWithChanges } from "@app/hooks/api/folderCommits";
import { useCommitRevert, useGetCommitDetails } from "@app/hooks/api/folderCommits/queries";
@@ -259,42 +264,53 @@ export const CommitDetailsTab = ({
</div>
</div>
<div className="flex items-center justify-start">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<IconButton
ariaLabel="commit-options"
variant="outline_bg"
className="h-10 rounded border border-mineshaft-600 bg-mineshaft-800 px-4 py-2 text-sm font-medium"
>
<p className="mr-2">Restore Options</p>
<FontAwesomeIcon icon={faAngleDown} />
</IconButton>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
sideOffset={2}
className="animate-in fade-in-50 zoom-in-95 min-w-[200px] p-1"
style={{ marginTop: "0" }}
>
<DropdownMenuItem
className="h-10 w-full cursor-pointer px-3 py-2 text-sm transition-colors hover:bg-mineshaft-700"
onClick={() => goToRollbackPreview()}
>
<div className="flex w-full items-center justify-start">
<span>Restore entire folder</span>
</div>
</DropdownMenuItem>
<ProjectPermissionCan
I={ProjectPermissionCommitsActions.Read}
a={ProjectPermissionSub.Commits}
>
{(isAllowed) => (
<DropdownMenu>
<DropdownMenuTrigger
asChild
disabled={!isAllowed}
className={`${!isAllowed ? "cursor-not-allowed" : ""}`}
>
<IconButton
ariaLabel="commit-options"
variant="outline_bg"
className="h-10 rounded border border-mineshaft-600 bg-mineshaft-800 px-4 py-2 text-sm font-medium"
>
<p className="mr-2">Restore Options</p>
<FontAwesomeIcon icon={faAngleDown} />
</IconButton>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
sideOffset={2}
className="animate-in fade-in-50 zoom-in-95 min-w-[200px] p-1"
style={{ marginTop: "0" }}
>
<DropdownMenuItem
className="h-10 w-full cursor-pointer px-3 py-2 text-sm transition-colors hover:bg-mineshaft-700"
onClick={() => goToRollbackPreview()}
>
<div className="flex w-full items-center justify-start">
<span>Restore entire folder</span>
</div>
</DropdownMenuItem>
<DropdownMenuItem
className="h-10 w-full cursor-pointer px-3 py-2 text-sm transition-colors hover:bg-mineshaft-700"
onClick={() => handlePopUpOpen("revertChanges")}
>
<div className="flex w-full items-center justify-start">
<span>Revert commit changes</span>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenuItem
className="h-10 w-full cursor-pointer px-3 py-2 text-sm transition-colors hover:bg-mineshaft-700"
onClick={() => handlePopUpOpen("revertChanges")}
>
<div className="flex w-full items-center justify-start">
<span>Revert commit changes</span>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</ProjectPermissionCan>
</div>
</div>
@@ -1,11 +1,9 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useSearch } from "@tanstack/react-router";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Badge, PageHeader, Tab, TabList, TabPanel, Tabs, Tooltip } from "@app/components/v2";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
@@ -54,16 +52,7 @@ export const IntegrationsListPage = () => {
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
<TabList>
<Tab value={IntegrationsListPageTabs.SecretSyncs}>Secret Syncs</Tab>
<Tab value={IntegrationsListPageTabs.NativeIntegrations}>
Native Integrations
<Tooltip content="Native Integrations will be deprecated in 2026. Please migrate to Secret Syncs as they become available.">
<div>
<Badge variant="primary" className="ml-1 cursor-pointer text-xs">
Legacy
</Badge>
</div>
</Tooltip>
</Tab>
<Tab value={IntegrationsListPageTabs.NativeIntegrations}>Native Integrations</Tab>
<Tab value={IntegrationsListPageTabs.FrameworkIntegrations}>
Framework Integrations
</Tab>
@@ -81,26 +70,6 @@ export const IntegrationsListPage = () => {
</ProjectPermissionCan>
</TabPanel>
<TabPanel value={IntegrationsListPageTabs.NativeIntegrations}>
<div className="mb-5 flex flex-col rounded-r border-l-2 border-l-primary bg-mineshaft-300/5 px-4 py-2.5">
<div className="mb-1 flex items-center text-sm">
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1.5 text-primary" />
Native Integrations Transitioning to Legacy Status
</div>
<p className="mb-2 mt-1 text-sm text-bunker-300">
Native integrations are now a legacy feature and we will begin a phased
deprecation in 2026. We recommend migrating to our new{" "}
<a
className="text-bunker-200 underline decoration-primary-700 underline-offset-4 duration-200 hover:text-mineshaft-100 hover:decoration-primary-600"
href="https://infisical.com/docs/integrations/secret-syncs/overview"
target="_blank"
rel="noopener noreferrer"
>
Secret Syncs
</a>{" "}
feature which offers the same functionality as Native Integrations with improved
stability, insights, re-configurability, and customization.
</p>
</div>
<ProjectPermissionCan
renderGuardBanner
I={ProjectPermissionActions.Read}
@@ -0,0 +1,14 @@
import { TOCIVaultSync } from "@app/hooks/api/secretSyncs/types/oci-vault-sync";
import { getSecretSyncDestinationColValues } from "../helpers";
import { SecretSyncTableCell } from "../SecretSyncTableCell";
type Props = {
secretSync: TOCIVaultSync;
};
export const OCIVaultSyncDestinationCol = ({ secretSync }: Props) => {
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
};
@@ -10,6 +10,7 @@ import { GcpSyncDestinationCol } from "./GcpSyncDestinationCol";
import { GitHubSyncDestinationCol } from "./GitHubSyncDestinationCol";
import { HCVaultSyncDestinationCol } from "./HCVaultSyncDestinationCol";
import { HumanitecSyncDestinationCol } from "./HumanitecSyncDestinationCol";
import { OCIVaultSyncDestinationCol } from "./OCIVaultSyncDestinationCol";
import { TeamCitySyncDestinationCol } from "./TeamCitySyncDestinationCol";
import { TerraformCloudSyncDestinationCol } from "./TerraformCloudSyncDestinationCol";
import { VercelSyncDestinationCol } from "./VercelSyncDestinationCol";
@@ -49,6 +50,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => {
return <HCVaultSyncDestinationCol secretSync={secretSync} />;
case SecretSync.TeamCity:
return <TeamCitySyncDestinationCol secretSync={secretSync} />;
case SecretSync.OCIVault:
return <OCIVaultSyncDestinationCol secretSync={secretSync} />;
default:
throw new Error(
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
@@ -102,6 +102,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
primaryText = destinationConfig.project;
secondaryText = destinationConfig.buildConfig;
break;
case SecretSync.OCIVault:
primaryText = destinationConfig.compartmentOcid;
secondaryText = destinationConfig.vaultOcid;
break;
default:
throw new Error(`Unhandled Destination Col Values ${destination}`);
}
@@ -251,7 +251,7 @@ export const AccessPolicyForm = ({
label="Policy Type"
isRequired
isError={Boolean(error)}
tooltipText="Change polices govern secret changes within a given environment and secret path. Access polices allow underprivileged user to request access to environment/secret path."
tooltipText="Change policies govern secret changes within a given environment and secret path. Access policies allow underprivileged user to request access to environment/secret path."
errorText={error?.message}
>
<Select
@@ -56,7 +56,10 @@ import {
useSubscription,
useWorkspace
} from "@app/context";
import { ProjectPermissionSecretRotationActions } from "@app/context/ProjectPermissionContext/types";
import {
ProjectPermissionCommitsActions,
ProjectPermissionSecretRotationActions
} from "@app/context/ProjectPermissionContext/types";
import { usePopUp } from "@app/hooks";
import {
useCreateFolder,
@@ -779,8 +782,8 @@ export const ActionBar = ({
</div>
<div>
<ProjectPermissionCan
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.SecretRollback}
I={isPITEnabled ? ProjectPermissionCommitsActions.Read : ProjectPermissionActions.Read}
a={isPITEnabled ? ProjectPermissionSub.Commits : ProjectPermissionSub.SecretRollback}
>
{(isAllowed) => (
<Button
@@ -6,6 +6,7 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Accordion,
AccordionContent,
@@ -18,9 +19,13 @@ import {
SecretInput,
Select,
SelectItem,
TextArea
TextArea,
Tooltip
} from "@app/components/v2";
import { useWorkspace } from "@app/context";
import {
OrgGatewayPermissionActions,
OrgPermissionSubjects
} from "@app/context/OrgPermissionContext/types";
import { gatewaysQueryKeys, useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders, SqlProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
@@ -61,7 +66,7 @@ const formSchema = z.object({
revocationStatement: z.string().min(1),
renewStatement: z.string().optional(),
ca: z.string().optional(),
projectGatewayId: z.string().optional()
gatewayId: z.string().optional()
}),
defaultTTL: z.string().superRefine((val, ctx) => {
const valMs = ms(val);
@@ -164,8 +169,6 @@ export const SqlDatabaseInputForm = ({
projectSlug,
isSingleEnvironmentMode
}: Props) => {
const { currentWorkspace } = useWorkspace();
const {
control,
setValue,
@@ -193,9 +196,7 @@ export const SqlDatabaseInputForm = ({
});
const createDynamicSecret = useCreateDynamicSecret();
const { data: projectGateways, isPending: isProjectGatewaysLoading } = useQuery(
gatewaysQueryKeys.listProjectGateways({ projectId: currentWorkspace.id })
);
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
const handleCreateDynamicSecret = async ({
name,
@@ -301,40 +302,55 @@ export const SqlDatabaseInputForm = ({
Configuration
</div>
<div>
<Controller
control={control}
name="provider.projectGatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
>
<Select
value={value}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isProjectGatewaysLoading}
placeholder="Internet gateway"
position="popper"
>
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
{(isAllowed) => (
<Controller
control={control}
name="provider.gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
>
Internet Gateway
</SelectItem>
{projectGateways?.map((el) => (
<SelectItem value={el.projectGatewayId} key={el.projectGatewayId}>
{el.name}
</SelectItem>
))}
</Select>
</FormControl>
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
>
<div>
<Select
isDisabled={!isAllowed}
value={value}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewaysLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
>
Internet Gateway
</SelectItem>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
/>
</OrgPermissionCan>
</div>
<div className="flex flex-col">
<div className="pb-0.5 pl-1 text-sm text-mineshaft-400">Service</div>
@@ -163,8 +163,8 @@ export const CreateSecretImportForm = ({
onValueChange={(val) => onChange(val === "true")}
className="w-full border border-mineshaft-500"
>
<SelectItem value="false">Ignore secret approval polices</SelectItem>
<SelectItem value="true">Respect secret approval polices</SelectItem>
<SelectItem value="false">Ignore secret approval policies</SelectItem>
<SelectItem value="true">Respect secret approval policies</SelectItem>
</Select>
</FormControl>
)}
@@ -6,6 +6,7 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Accordion,
AccordionContent,
@@ -17,9 +18,11 @@ import {
SecretInput,
Select,
SelectItem,
TextArea
TextArea,
Tooltip
} from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { OrgPermissionSubjects } from "@app/context";
import { OrgGatewayPermissionActions } from "@app/context/OrgPermissionContext/types";
import { gatewaysQueryKeys, useUpdateDynamicSecret } from "@app/hooks/api";
import { SqlProviders, TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
@@ -60,7 +63,7 @@ const formSchema = z.object({
revocationStatement: z.string().min(1),
renewStatement: z.string().optional(),
ca: z.string().optional(),
projectGatewayId: z.string().optional().nullable()
gatewayId: z.string().optional().nullable()
})
.partial(),
defaultTTL: z.string().superRefine((val, ctx) => {
@@ -147,15 +150,11 @@ export const EditDynamicSecretSqlProviderForm = ({
}
});
const { currentWorkspace } = useWorkspace();
const { data: projectGateways, isPending: isProjectGatewaysLoading } = useQuery(
gatewaysQueryKeys.listProjectGateways({ projectId: currentWorkspace.id })
);
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
const updateDynamicSecret = useUpdateDynamicSecret();
const selectedProjectGatewayId = watch("inputs.projectGatewayId");
const isGatewayInActive =
projectGateways?.findIndex((el) => el.projectGatewayId === selectedProjectGatewayId) === -1;
const selectedGatewayId = watch("inputs.gatewayId");
const isGatewayInActive = gateways?.findIndex((el) => el.id === selectedGatewayId) === -1;
const handleUpdateDynamicSecret = async ({
inputs,
@@ -177,7 +176,7 @@ export const EditDynamicSecretSqlProviderForm = ({
defaultTTL,
inputs: {
...inputs,
projectGatewayId: isGatewayInActive ? null : inputs.projectGatewayId
gatewayId: isGatewayInActive ? null : inputs.gatewayId
},
newName: newName === dynamicSecret.name ? undefined : newName,
metadata
@@ -250,45 +249,60 @@ export const EditDynamicSecretSqlProviderForm = ({
<div>
<div className="mb-4 border-b border-b-mineshaft-600 pb-2">Configuration</div>
<div>
<Controller
control={control}
name="inputs.projectGatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message) || isGatewayInActive}
errorText={
isGatewayInActive && selectedProjectGatewayId
? `Project Gateway ${selectedProjectGatewayId} is removed`
: error?.message
}
label="Gateway"
helperText=""
>
<Select
value={value || undefined}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isProjectGatewaysLoading}
placeholder="Internet Gateway"
position="popper"
>
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
{(isAllowed) => (
<Controller
control={control}
name="inputs.gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message) || isGatewayInActive}
errorText={
isGatewayInActive && selectedGatewayId
? `Project Gateway ${selectedGatewayId} is removed`
: error?.message
}
label="Gateway"
helperText=""
>
Internet Gateway
</SelectItem>
{projectGateways?.map((el) => (
<SelectItem value={el.projectGatewayId} key={el.id}>
{el.name}
</SelectItem>
))}
</Select>
</FormControl>
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
>
<div>
<Select
isDisabled={!isAllowed}
value={value || undefined}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewaysLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
>
Internet Gateway
</SelectItem>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
/>
</OrgPermissionCan>
</div>
<div className="flex flex-col">
<Controller
@@ -95,7 +95,6 @@ export const SecretDetailSidebar = ({
handleSecretShare
}: Props) => {
const {
register,
control,
watch,
handleSubmit,
@@ -104,7 +103,8 @@ export const SecretDetailSidebar = ({
formState: { isDirty, isSubmitting }
} = useForm<TFormSchema>({
resolver: zodResolver(formSchema),
values: secret
values: secret,
disabled: !secret
});
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([
@@ -398,11 +398,19 @@ export const SecretDetailSidebar = ({
autoFocus={false}
/>
<Tooltip
content="You don't have permission to view the secret value."
isDisabled={!secret?.secretValueHidden}
content={
!currentWorkspace.secretSharing
? "This project does not allow secret sharing."
: "You don't have permission to view the secret value."
}
isDisabled={
!secret?.secretValueHidden && currentWorkspace.secretSharing
}
>
<Button
isDisabled={secret?.secretValueHidden}
isDisabled={
secret?.secretValueHidden || !currentWorkspace.secretSharing
}
className="px-2 py-[0.43rem] font-normal"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faShare} />}
@@ -705,14 +713,25 @@ export const SecretDetailSidebar = ({
</FormControl>
</div>
</div>
<FormControl label="Comments & Notes">
<TextArea
className="border border-mineshaft-600 bg-bunker-800 text-sm"
{...register("comment")}
readOnly={isReadOnly}
rows={5}
/>
</FormControl>
<Controller
control={control}
name="comment"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Comments & Notes"
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0"
>
<TextArea
className="border border-mineshaft-600 bg-bunker-800 text-sm"
readOnly={isReadOnly}
rows={5}
{...field}
/>
</FormControl>
)}
/>
<FormControl>
{secretReminderRepeatDays && secretReminderRepeatDays > 0 ? (
<div className="flex items-center justify-between px-2">
@@ -913,7 +932,9 @@ export const SecretDetailSidebar = ({
variant="outline_bg"
size="sm"
className="h-8 w-8 rounded-md"
onClick={() => setValue("value", secretValue)}
onClick={() =>
setValue("value", secretValue, { shouldDirty: true })
}
>
<FontAwesomeIcon icon={faArrowRotateRight} />
</IconButton>
@@ -589,7 +589,7 @@ export const SecretItem = memo(
)}
</ProjectPermissionCan>
<IconButton
isDisabled={secret.secretValueHidden}
isDisabled={secret.secretValueHidden || !currentWorkspace.secretSharing}
className="w-0 overflow-hidden p-0 group-hover:w-5"
variant="plain"
size="md"
@@ -0,0 +1,42 @@
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { Tooltip } from "@app/components/v2";
import { TOCIVaultSync } from "@app/hooks/api/secretSyncs/types/oci-vault-sync";
type Props = {
secretSync: TOCIVaultSync;
};
export const OCIVaultSyncDestinationSection = ({ secretSync }: Props) => {
const {
destinationConfig: { compartmentOcid, keyOcid, vaultOcid }
} = secretSync;
return (
<>
<Tooltip side="bottom" className="max-w-sm select-text break-words" content={compartmentOcid}>
<div>
<GenericFieldLabel label="Compartment OCID">
{compartmentOcid.substring(0, 21)}...
{compartmentOcid.substring(compartmentOcid.length - 6)}
</GenericFieldLabel>
</div>
</Tooltip>
<Tooltip side="bottom" className="max-w-sm select-text break-words" content={compartmentOcid}>
<div>
<GenericFieldLabel label="Vault OCID">
{vaultOcid.substring(0, 15)}...
{vaultOcid.substring(vaultOcid.length - 6)}
</GenericFieldLabel>
</div>
</Tooltip>
<Tooltip side="bottom" className="max-w-sm select-text break-words" content={compartmentOcid}>
<div>
<GenericFieldLabel label="Key OCID">
{keyOcid.substring(0, 13)}...
{keyOcid.substring(keyOcid.length - 6)}
</GenericFieldLabel>
</div>
</Tooltip>
</>
);
};
@@ -20,6 +20,7 @@ import { GcpSyncDestinationSection } from "./GcpSyncDestinationSection";
import { GitHubSyncDestinationSection } from "./GitHubSyncDestinationSection";
import { HCVaultSyncDestinationSection } from "./HCVaultSyncDestinationSection";
import { HumanitecSyncDestinationSection } from "./HumanitecSyncDestinationSection";
import { OCIVaultSyncDestinationSection } from "./OCIVaultSyncDestinationSection";
import { TeamCitySyncDestinationSection } from "./TeamCitySyncDestinationSection";
import { TerraformCloudSyncDestinationSection } from "./TerraformCloudSyncDestinationSection";
import { VercelSyncDestinationSection } from "./VercelSyncDestinationSection";
@@ -81,6 +82,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }:
case SecretSync.TeamCity:
DestinationComponents = <TeamCitySyncDestinationSection secretSync={secretSync} />;
break;
case SecretSync.OCIVault:
DestinationComponents = <OCIVaultSyncDestinationSection secretSync={secretSync} />;
break;
default:
throw new Error(`Unhandled Destination Section components: ${destination}`);
}
@@ -21,12 +21,7 @@ type Props = {
export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) => {
const {
destination,
syncOptions: {
// appendSuffix,
// prependPrefix,
initialSyncBehavior,
disableSecretDeletion
}
syncOptions: { initialSyncBehavior, disableSecretDeletion, keySchema }
} = secretSync;
let AdditionalSyncOptionsComponent: ReactNode;
@@ -54,6 +49,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
case SecretSync.Windmill:
case SecretSync.HCVault:
case SecretSync.TeamCity:
case SecretSync.OCIVault:
AdditionalSyncOptionsComponent = null;
break;
default:
@@ -87,8 +83,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
<GenericFieldLabel label="Initial Sync Behavior">
{SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP[initialSyncBehavior](destination).name}
</GenericFieldLabel>
{/* <SecretSyncLabel label="Prefix">{prependPrefix}</SecretSyncLabel>
<SecretSyncLabel label="Suffix">{appendSuffix}</SecretSyncLabel> */}
<GenericFieldLabel label="Key Schema">{keySchema}</GenericFieldLabel>
{AdditionalSyncOptionsComponent}
{disableSecretDeletion && (
<GenericFieldLabel label="Secret Deletion">
@@ -10,6 +10,7 @@ import { DeleteProjectSection } from "../DeleteProjectSection";
import { EnvironmentSection } from "../EnvironmentSection";
import { PointInTimeVersionLimitSection } from "../PointInTimeVersionLimitSection";
import { RebuildSecretIndicesSection } from "../RebuildSecretIndicesSection/RebuildSecretIndicesSection";
import { SecretSharingSection } from "../SecretSharingSection";
import { SecretTagsSection } from "../SecretTagsSection";
export const ProjectGeneralTab = () => {
@@ -22,6 +23,7 @@ export const ProjectGeneralTab = () => {
{isSecretManager && <EnvironmentSection />}
{isSecretManager && <SecretTagsSection />}
{isSecretManager && <AutoCapitalizationSection />}
{isSecretManager && <SecretSharingSection />}
{isSecretManager && <PointInTimeVersionLimitSection />}
<AuditLogsRetentionSection />
{isSecretManager && <BackfillSecretReferenceSecretion />}
@@ -0,0 +1,64 @@
import { useState } from "react";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Checkbox } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useUpdateProject } from "@app/hooks/api/workspace/queries";
export const SecretSharingSection = () => {
const { currentWorkspace } = useWorkspace();
const { mutateAsync: updateProject } = useUpdateProject();
const [isLoading, setIsLoading] = useState(false);
const handleToggle = async (state: boolean) => {
setIsLoading(true);
try {
if (!currentWorkspace?.id) {
setIsLoading(false);
return;
}
await updateProject({
projectID: currentWorkspace.id,
secretSharing: state
});
createNotification({
text: `Successfully ${state ? "enabled" : "disabled"} secret sharing for this project`,
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to update secret sharing for this project",
type: "error"
});
} finally {
setIsLoading(false);
}
};
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-3 text-xl font-semibold">Allow Secret Sharing</p>
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
{(isAllowed) => (
<div className="w-max">
<Checkbox
className="data-[state=checked]:bg-primary"
id="secretSharing"
isDisabled={!isAllowed || isLoading}
isChecked={currentWorkspace?.secretSharing ?? true}
onCheckedChange={(state) => handleToggle(state as boolean)}
>
This feature enables your project members to securely share secrets.
</Checkbox>
</div>
)}
</ProjectPermissionCan>
</div>
);
};
@@ -0,0 +1 @@
export { SecretSharingSection } from "./SecretSharingSection";