mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 12:27:28 +00:00
improve discoverability and UX for replicate folders/secrets feature
This commit is contained in:
@@ -49,3 +49,23 @@ In the image below, you can see that the **Development** environment is the only
|
|||||||
in the `/users` folder, being other folders `/user-a`, `/user-b`, ... `/user-f`.
|
in the `/users` folder, being other folders `/user-a`, `/user-b`, ... `/user-f`.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Replicating Folder Contents
|
||||||
|
|
||||||
|
If you want to copy secrets or folders from one path to another, you can utilize the **Replicate Secrets** functionality located in the **Add Secret** dropdown.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
First, select the **Source Environment** and the **Source Root Path** you want to copy secrets *from*. In the example provided, we select `/dev-folder` as the source root path from the Development environment. This means any secrets within `/dev-folder` from Development will be replicated. By default, these secrets are copied into the *currently active* folder/path in your target environment (e.g., the root folder of your Staging environment in this scenario).
|
||||||
|
|
||||||
|
As a final step, you can select the specific secrets you wish to copy and then click **Replicate Secrets**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The result shows two secrets successfully copied from the `/dev-folder` in the Development environment into the root folder of the Staging environment.
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
If you do not select a **Source Root Path**, the replication will consider the contents of the *entire root* of the **Source Environment** (e.g., the Development environment). In this example that would mean copying the `/dev-folder` itself rather than just it's contents.
|
||||||
|
</Info>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 577 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 919 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 934 KiB |
+8
-3
@@ -380,7 +380,7 @@ export const ActionBar = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Replicate Folder Logic
|
// Replicate Secrets Logic
|
||||||
const createSecretCount = Object.keys(
|
const createSecretCount = Object.keys(
|
||||||
(popUp.confirmUpload?.data as TSecOverwriteOpt)?.create || {}
|
(popUp.confirmUpload?.data as TSecOverwriteOpt)?.create || {}
|
||||||
).length;
|
).length;
|
||||||
@@ -439,7 +439,7 @@ export const ActionBar = ({
|
|||||||
const processBatches = async () => {
|
const processBatches = async () => {
|
||||||
await secretBatches.reduce(async (previous, batch) => {
|
await secretBatches.reduce(async (previous, batch) => {
|
||||||
await previous;
|
await previous;
|
||||||
|
try {
|
||||||
const { secrets: batchSecrets } = await fetchDashboardProjectSecretsByKeys({
|
const { secrets: batchSecrets } = await fetchDashboardProjectSecretsByKeys({
|
||||||
secretPath: normalizedPath,
|
secretPath: normalizedPath,
|
||||||
environment,
|
environment,
|
||||||
@@ -450,6 +450,11 @@ export const ActionBar = ({
|
|||||||
batchSecrets.forEach((secret) => {
|
batchSecrets.forEach((secret) => {
|
||||||
existingSecretLookup.add(`${normalizedPath}-${secret.secretKey}`);
|
existingSecretLookup.add(`${normalizedPath}-${secret.secretKey}`);
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (!(error instanceof AxiosError && error.response?.status === 404)) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}, Promise.resolve());
|
}, Promise.resolve());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1050,7 +1055,7 @@ export const ActionBar = ({
|
|||||||
className="h-10 text-left"
|
className="h-10 text-left"
|
||||||
isFullWidth
|
isFullWidth
|
||||||
>
|
>
|
||||||
Replicate Folder
|
Replicate Secrets
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
|
|||||||
+43
-9
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { faClone } from "@fortawesome/free-solid-svg-icons";
|
import { faArrowUpRightFromSquare, faBookOpen, faClone } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -226,8 +226,27 @@ export const ReplicateFolderFromBoard = ({
|
|||||||
<ModalContent
|
<ModalContent
|
||||||
bodyClassName="overflow-visible"
|
bodyClassName="overflow-visible"
|
||||||
className="max-w-2xl"
|
className="max-w-2xl"
|
||||||
title="Replicate Folder Content From An Environment"
|
title={
|
||||||
subTitle="Replicate folder content from other environments into this context"
|
<div className="flex items-center gap-1 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="mb-[0.03rem] mr-1 text-[12px]" />
|
||||||
|
<span>Docs</span>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.07rem] ml-1 text-[10px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
subTitle="Copy folder contents from other locations into this context"
|
||||||
>
|
>
|
||||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
@@ -235,7 +254,12 @@ export const ReplicateFolderFromBoard = ({
|
|||||||
control={control}
|
control={control}
|
||||||
name="environment"
|
name="environment"
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<FormControl label="Environment" isRequired className="w-1/3">
|
<FormControl
|
||||||
|
tooltipText="The environment to replicate secrets from"
|
||||||
|
label="Source Environment"
|
||||||
|
isRequired
|
||||||
|
className="w-1/3"
|
||||||
|
>
|
||||||
<FilterableSelect
|
<FilterableSelect
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
@@ -251,7 +275,12 @@ export const ReplicateFolderFromBoard = ({
|
|||||||
control={control}
|
control={control}
|
||||||
name="secretPath"
|
name="secretPath"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl label="Secret Path" className="flex-grow" isRequired>
|
<FormControl
|
||||||
|
tooltipText="The folder to use as a root for replication. Using /foo as a root for /foo/bar will result in /bar being copied to this context."
|
||||||
|
label="Source Root Path"
|
||||||
|
className="flex-grow"
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
<SecretPathInput
|
<SecretPathInput
|
||||||
{...field}
|
{...field}
|
||||||
placeholder="Provide a path, default is /"
|
placeholder="Provide a path, default is /"
|
||||||
@@ -261,12 +290,16 @@ export const ReplicateFolderFromBoard = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-t border-mineshaft-600 pt-4">
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="secrets"
|
name="secrets"
|
||||||
render={({ field: { onChange } }) => (
|
render={({ field: { onChange } }) => (
|
||||||
<FormControl className="flex-grow" isRequired>
|
<FormControl
|
||||||
|
tooltipText="The folders and secrets to replicate into this context"
|
||||||
|
className="flex-grow"
|
||||||
|
label="Affected Subjects"
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
<SecretTreeView
|
<SecretTreeView
|
||||||
data={secretsFilteredByPath}
|
data={secretsFilteredByPath}
|
||||||
basePath={debouncedEnvCopySecretPath}
|
basePath={debouncedEnvCopySecretPath}
|
||||||
@@ -291,15 +324,16 @@ export const ReplicateFolderFromBoard = ({
|
|||||||
<Button
|
<Button
|
||||||
leftIcon={<FontAwesomeIcon icon={faClone} />}
|
leftIcon={<FontAwesomeIcon icon={faClone} />}
|
||||||
type="submit"
|
type="submit"
|
||||||
|
variant="outline_bg"
|
||||||
|
colorSchema="secondary"
|
||||||
isDisabled={!selectedSecrets || selectedSecrets.length === 0}
|
isDisabled={!selectedSecrets || selectedSecrets.length === 0}
|
||||||
>
|
>
|
||||||
Replicate Folder
|
Replicate Secrets
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="plain" colorSchema="secondary" onClick={() => onToggle(false)}>
|
<Button variant="plain" colorSchema="secondary" onClick={() => onToggle(false)}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
Reference in New Issue
Block a user