mirror of
https://github.com/Infisical/infisical.git
synced 2025-08-13 07:12:51 +00:00
Compare commits
5 Commits
daniel/upd
...
environmen
Author | SHA1 | Date | |
---|---|---|---|
|
6905ffba4e | ||
|
64fd423c61 | ||
|
da1a7466d1 | ||
|
d3f3f34129 | ||
|
c8fba7ce4c |
@@ -58,6 +58,7 @@ export const FilterableSelect = <T,>({
|
|||||||
clearIndicator: () => "p-1 hover:text-red text-bunker-400",
|
clearIndicator: () => "p-1 hover:text-red text-bunker-400",
|
||||||
indicatorSeparator: () => "bg-bunker-400",
|
indicatorSeparator: () => "bg-bunker-400",
|
||||||
dropdownIndicator: () => "text-bunker-200 p-1",
|
dropdownIndicator: () => "text-bunker-200 p-1",
|
||||||
|
menuList: () => "flex flex-col gap-1",
|
||||||
menu: () =>
|
menu: () =>
|
||||||
"mt-2 p-2 border text-sm text-mineshaft-200 thin-scrollbar bg-mineshaft-900 border-mineshaft-600 rounded-md",
|
"mt-2 p-2 border text-sm text-mineshaft-200 thin-scrollbar bg-mineshaft-900 border-mineshaft-600 rounded-md",
|
||||||
groupHeading: () => "ml-3 mt-2 mb-1 text-mineshaft-400 text-sm",
|
groupHeading: () => "ml-3 mt-2 mb-1 text-mineshaft-400 text-sm",
|
||||||
@@ -65,7 +66,7 @@ export const FilterableSelect = <T,>({
|
|||||||
twMerge(
|
twMerge(
|
||||||
isFocused && "bg-mineshaft-700 active:bg-mineshaft-600",
|
isFocused && "bg-mineshaft-700 active:bg-mineshaft-600",
|
||||||
isSelected && "text-mineshaft-200",
|
isSelected && "text-mineshaft-200",
|
||||||
"hover:cursor-pointer mb-1 rounded text-xs px-3 py-2"
|
"hover:cursor-pointer rounded text-xs px-3 py-2"
|
||||||
),
|
),
|
||||||
noOptionsMessage: () => "text-mineshaft-400 p-2 rounded-md"
|
noOptionsMessage: () => "text-mineshaft-400 p-2 rounded-md"
|
||||||
}}
|
}}
|
||||||
|
@@ -54,7 +54,7 @@ export const Pagination = ({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{startAdornment}
|
{startAdornment}
|
||||||
<div className="ml-auto mr-6 flex items-center space-x-2">
|
<div className={twMerge("mr-4 flex items-center space-x-2", startAdornment && "ml-auto")}>
|
||||||
<div className="text-xs">
|
<div className="text-xs">
|
||||||
{(page - 1) * perPage + 1} - {Math.min((page - 1) * perPage + perPage, count)} of {count}
|
{(page - 1) * perPage + 1} - {Math.min((page - 1) * perPage + perPage, count)} of {count}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -876,7 +876,7 @@ const OrganizationPage = () => {
|
|||||||
<Pagination
|
<Pagination
|
||||||
className={
|
className={
|
||||||
projectsViewMode === ProjectsViewMode.GRID
|
projectsViewMode === ProjectsViewMode.GRID
|
||||||
? "col-span-full border-transparent bg-transparent"
|
? "col-span-full !justify-start border-transparent bg-transparent pl-2"
|
||||||
: "rounded-b-md border border-mineshaft-600"
|
: "rounded-b-md border border-mineshaft-600"
|
||||||
}
|
}
|
||||||
perPage={perPage}
|
perPage={perPage}
|
||||||
|
@@ -6,6 +6,7 @@ import { z } from "zod";
|
|||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
FilterableSelect,
|
||||||
FormControl,
|
FormControl,
|
||||||
Modal,
|
Modal,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
@@ -17,7 +18,7 @@ import { useSubscription, useWorkspace } from "@app/context";
|
|||||||
import { useCreateSecretImport } from "@app/hooks/api";
|
import { useCreateSecretImport } from "@app/hooks/api";
|
||||||
|
|
||||||
const typeSchema = z.object({
|
const typeSchema = z.object({
|
||||||
environment: z.string().trim(),
|
environment: z.object({ name: z.string(), slug: z.string() }),
|
||||||
secretPath: z
|
secretPath: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
@@ -80,7 +81,7 @@ export const CreateSecretImportForm = ({
|
|||||||
path: secretPath,
|
path: secretPath,
|
||||||
isReplication,
|
isReplication,
|
||||||
import: {
|
import: {
|
||||||
environment: importedEnv,
|
environment: importedEnv.slug,
|
||||||
path: importedSecPath
|
path: importedSecPath
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -88,8 +89,9 @@ export const CreateSecretImportForm = ({
|
|||||||
reset();
|
reset();
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "success",
|
type: "success",
|
||||||
text: `Successfully linked. ${isReplication ? "Please refresh the dashboard to view changes" : ""
|
text: `Successfully linked. ${
|
||||||
}`
|
isReplication ? "Please refresh the dashboard to view changes" : ""
|
||||||
|
}`
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -111,6 +113,7 @@ export const CreateSecretImportForm = ({
|
|||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onOpenChange={onTogglePopUp}>
|
<Modal isOpen={isOpen} onOpenChange={onTogglePopUp}>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
|
bodyClassName="overflow-visible"
|
||||||
title="Add Secret Link"
|
title="Add Secret Link"
|
||||||
subTitle="To inherit secrets from another environment or folder"
|
subTitle="To inherit secrets from another environment or folder"
|
||||||
>
|
>
|
||||||
@@ -118,21 +121,16 @@ export const CreateSecretImportForm = ({
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="environment"
|
name="environment"
|
||||||
defaultValue={environments?.[0]?.slug}
|
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
||||||
<FormControl label="Environment" errorText={error?.message} isError={Boolean(error)}>
|
<FormControl label="Environment" errorText={error?.message} isError={Boolean(error)}>
|
||||||
<Select
|
<FilterableSelect
|
||||||
defaultValue={field.value}
|
options={environments}
|
||||||
{...field}
|
getOptionLabel={(option) => option.name}
|
||||||
onValueChange={(e) => onChange(e)}
|
getOptionValue={(option) => option.slug}
|
||||||
className="w-full"
|
placeholder="Select environment..."
|
||||||
>
|
value={value}
|
||||||
{environments.map(({ name, slug }) => (
|
onChange={onChange}
|
||||||
<SelectItem value={slug} key={slug}>
|
/>
|
||||||
{name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -142,7 +140,7 @@ export const CreateSecretImportForm = ({
|
|||||||
defaultValue="/"
|
defaultValue="/"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl label="Secret Path" isError={Boolean(error)} errorText={error?.message}>
|
<FormControl label="Secret Path" isError={Boolean(error)} errorText={error?.message}>
|
||||||
<SecretPathInput {...field} environment={selectedEnvironment} />
|
<SecretPathInput {...field} environment={selectedEnvironment?.slug} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@@ -1,14 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { subject } from "@casl/ability";
|
import { subject } from "@casl/ability";
|
||||||
import {
|
import { faClone, faFileImport, faSquareCheck } from "@fortawesome/free-solid-svg-icons";
|
||||||
faClone,
|
|
||||||
faFileImport,
|
|
||||||
faKey,
|
|
||||||
faSearch,
|
|
||||||
faSquareCheck,
|
|
||||||
faSquareXmark
|
|
||||||
} 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";
|
||||||
@@ -16,17 +9,13 @@ import { z } from "zod";
|
|||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
FilterableSelect,
|
||||||
EmptyState,
|
|
||||||
FormControl,
|
FormControl,
|
||||||
IconButton,
|
IconButton,
|
||||||
Input,
|
|
||||||
Modal,
|
Modal,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
ModalTrigger,
|
ModalTrigger,
|
||||||
Select,
|
Switch,
|
||||||
SelectItem,
|
|
||||||
Skeleton,
|
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||||
@@ -35,14 +24,17 @@ import { useDebounce } from "@app/hooks";
|
|||||||
import { useGetProjectSecrets } from "@app/hooks/api";
|
import { useGetProjectSecrets } from "@app/hooks/api";
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
environment: z.string().trim(),
|
environment: z.object({ name: z.string(), slug: z.string() }),
|
||||||
secretPath: z
|
secretPath: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.transform((val) =>
|
.transform((val) =>
|
||||||
typeof val === "string" && val.at(-1) === "/" && val.length > 1 ? val.slice(0, -1) : val
|
typeof val === "string" && val.at(-1) === "/" && val.length > 1 ? val.slice(0, -1) : val
|
||||||
),
|
),
|
||||||
secrets: z.record(z.string().optional().nullable())
|
secrets: z
|
||||||
|
.object({ key: z.string(), value: z.string().optional() })
|
||||||
|
.array()
|
||||||
|
.min(1, "Select one or more secrets to copy")
|
||||||
});
|
});
|
||||||
|
|
||||||
type TFormSchema = z.infer<typeof formSchema>;
|
type TFormSchema = z.infer<typeof formSchema>;
|
||||||
@@ -68,7 +60,6 @@ export const CopySecretsFromBoard = ({
|
|||||||
onToggle,
|
onToggle,
|
||||||
onParsedEnv
|
onParsedEnv
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [searchFilter, setSearchFilter] = useState("");
|
|
||||||
const [shouldIncludeValues, setShouldIncludeValues] = useState(true);
|
const [shouldIncludeValues, setShouldIncludeValues] = useState(true);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -80,7 +71,7 @@ export const CopySecretsFromBoard = ({
|
|||||||
formState: { isDirty }
|
formState: { isDirty }
|
||||||
} = useForm<TFormSchema>({
|
} = useForm<TFormSchema>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: { secretPath: "/", environment: environments?.[0]?.slug }
|
defaultValues: { secretPath: "/", environment: environments?.[0] }
|
||||||
});
|
});
|
||||||
|
|
||||||
const envCopySecPath = watch("secretPath");
|
const envCopySecPath = watch("secretPath");
|
||||||
@@ -89,7 +80,7 @@ export const CopySecretsFromBoard = ({
|
|||||||
|
|
||||||
const { data: secrets, isLoading: isSecretsLoading } = useGetProjectSecrets({
|
const { data: secrets, isLoading: isSecretsLoading } = useGetProjectSecrets({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment: selectedEnvSlug,
|
environment: selectedEnvSlug.slug,
|
||||||
secretPath: debouncedEnvCopySecretPath,
|
secretPath: debouncedEnvCopySecretPath,
|
||||||
options: {
|
options: {
|
||||||
enabled:
|
enabled:
|
||||||
@@ -101,29 +92,22 @@ export const CopySecretsFromBoard = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue("secrets", {});
|
setValue("secrets", []);
|
||||||
setSearchFilter("");
|
}, [debouncedEnvCopySecretPath, selectedEnvSlug]);
|
||||||
}, [debouncedEnvCopySecretPath]);
|
|
||||||
|
|
||||||
const handleSecSelectAll = () => {
|
const handleSecSelectAll = () => {
|
||||||
if (secrets) {
|
if (secrets) {
|
||||||
setValue(
|
setValue("secrets", secrets, { shouldDirty: true });
|
||||||
"secrets",
|
|
||||||
secrets?.reduce((prev, curr) => ({ ...prev, [curr.key]: curr.value }), {}),
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFormSubmit = async (data: TFormSchema) => {
|
const handleFormSubmit = async (data: TFormSchema) => {
|
||||||
const secretsToBePulled: Record<string, { value: string; comments: string[] }> = {};
|
const secretsToBePulled: Record<string, { value: string; comments: string[] }> = {};
|
||||||
Object.keys(data.secrets || {}).forEach((key) => {
|
data.secrets.forEach(({ key, value }) => {
|
||||||
if (data.secrets[key]) {
|
secretsToBePulled[key] = {
|
||||||
secretsToBePulled[key] = {
|
value: (shouldIncludeValues && value) || "",
|
||||||
value: (shouldIncludeValues && data.secrets[key]) || "",
|
comments: [""]
|
||||||
comments: [""]
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
onParsedEnv(secretsToBePulled);
|
onParsedEnv(secretsToBePulled);
|
||||||
onToggle(false);
|
onToggle(false);
|
||||||
@@ -136,7 +120,6 @@ export const CopySecretsFromBoard = ({
|
|||||||
onOpenChange={(state) => {
|
onOpenChange={(state) => {
|
||||||
onToggle(state);
|
onToggle(state);
|
||||||
reset();
|
reset();
|
||||||
setSearchFilter("");
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ModalTrigger asChild>
|
<ModalTrigger asChild>
|
||||||
@@ -165,6 +148,7 @@ export const CopySecretsFromBoard = ({
|
|||||||
</div>
|
</div>
|
||||||
</ModalTrigger>
|
</ModalTrigger>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
|
bodyClassName="overflow-visible"
|
||||||
className="max-w-2xl"
|
className="max-w-2xl"
|
||||||
title="Copy Secret From An Environment"
|
title="Copy Secret From An Environment"
|
||||||
subTitle="Copy/paste secrets from other environments into this context"
|
subTitle="Copy/paste secrets from other environments into this context"
|
||||||
@@ -176,22 +160,14 @@ export const CopySecretsFromBoard = ({
|
|||||||
name="environment"
|
name="environment"
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<FormControl label="Environment" isRequired className="w-1/3">
|
<FormControl label="Environment" isRequired className="w-1/3">
|
||||||
<Select
|
<FilterableSelect
|
||||||
value={value}
|
value={value}
|
||||||
onValueChange={(val) => onChange(val)}
|
onChange={onChange}
|
||||||
className="w-full border border-mineshaft-500"
|
options={environments}
|
||||||
defaultValue={environments?.[0]?.slug}
|
placeholder="Select environment..."
|
||||||
position="popper"
|
getOptionLabel={(option) => option.name}
|
||||||
>
|
getOptionValue={(option) => option.slug}
|
||||||
{environments.map((sourceEnvironment) => (
|
/>
|
||||||
<SelectItem
|
|
||||||
value={sourceEnvironment.slug}
|
|
||||||
key={`source-environment-${sourceEnvironment.slug}`}
|
|
||||||
>
|
|
||||||
{sourceEnvironment.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -203,7 +179,7 @@ export const CopySecretsFromBoard = ({
|
|||||||
<SecretPathInput
|
<SecretPathInput
|
||||||
{...field}
|
{...field}
|
||||||
placeholder="Provide a path, default is /"
|
placeholder="Provide a path, default is /"
|
||||||
environment={selectedEnvSlug}
|
environment={selectedEnvSlug?.slug}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
@@ -212,72 +188,57 @@ export const CopySecretsFromBoard = ({
|
|||||||
<div className="border-t border-mineshaft-600 pt-4">
|
<div className="border-t border-mineshaft-600 pt-4">
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<div>Secrets</div>
|
<div>Secrets</div>
|
||||||
<div className="flex w-1/2 items-center space-x-2">
|
</div>
|
||||||
<Input
|
<div className="flex w-full items-start gap-3">
|
||||||
placeholder="Search for secret"
|
<Controller
|
||||||
value={searchFilter}
|
control={control}
|
||||||
|
name="secrets"
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
className="flex-1"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
placeholder={
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
isSecretsLoading
|
||||||
|
? "Loading secrets..."
|
||||||
|
: secrets?.length
|
||||||
|
? "Select secrets..."
|
||||||
|
: "No secrets found..."
|
||||||
|
}
|
||||||
|
isLoading={isSecretsLoading}
|
||||||
|
options={secrets}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
isMulti
|
||||||
|
getOptionValue={(option) => option.key}
|
||||||
|
getOptionLabel={(option) => option.key}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Tooltip content="Select All">
|
||||||
|
<IconButton
|
||||||
|
className="mt-1 h-9 w-9"
|
||||||
|
ariaLabel="Select all"
|
||||||
|
variant="outline_bg"
|
||||||
size="xs"
|
size="xs"
|
||||||
leftIcon={<FontAwesomeIcon icon={faSearch} />}
|
onClick={handleSecSelectAll}
|
||||||
onChange={(evt) => setSearchFilter(evt.target.value)}
|
>
|
||||||
/>
|
<FontAwesomeIcon icon={faSquareCheck} size="lg" />
|
||||||
<Tooltip content="Select All">
|
</IconButton>
|
||||||
<IconButton
|
</Tooltip>
|
||||||
ariaLabel="Select all"
|
|
||||||
variant="outline_bg"
|
|
||||||
size="xs"
|
|
||||||
onClick={handleSecSelectAll}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faSquareCheck} size="lg" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip content="Unselect All">
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="UnSelect all"
|
|
||||||
variant="outline_bg"
|
|
||||||
size="xs"
|
|
||||||
onClick={() => reset()}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faSquareXmark} size="lg" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{!isSecretsLoading && !secrets?.length && (
|
<div className="my-6 ml-2">
|
||||||
<EmptyState title="No secrets found" icon={faKey} />
|
<Switch
|
||||||
)}
|
|
||||||
<div className="thin-scrollbar grid max-h-64 grid-cols-2 gap-4 overflow-auto ">
|
|
||||||
{isSecretsLoading &&
|
|
||||||
Array.apply(0, Array(2)).map((_x, i) => (
|
|
||||||
<Skeleton key={`secret-pull-loading-${i + 1}`} className="bg-mineshaft-700" />
|
|
||||||
))}
|
|
||||||
|
|
||||||
{secrets
|
|
||||||
?.filter(({ key }) => key.toLowerCase().includes(searchFilter.toLowerCase()))
|
|
||||||
?.map(({ id, key, value: secVal }) => (
|
|
||||||
<Controller
|
|
||||||
key={`pull-secret--${id}`}
|
|
||||||
control={control}
|
|
||||||
name={`secrets.${key}`}
|
|
||||||
render={({ field: { value, onChange } }) => (
|
|
||||||
<Checkbox
|
|
||||||
id={`pull-secret-${id}`}
|
|
||||||
isChecked={Boolean(value)}
|
|
||||||
onCheckedChange={(isChecked) => onChange(isChecked ? secVal : "")}
|
|
||||||
>
|
|
||||||
{key}
|
|
||||||
</Checkbox>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="mt-6 mb-4">
|
|
||||||
<Checkbox
|
|
||||||
id="populate-include-value"
|
id="populate-include-value"
|
||||||
isChecked={shouldIncludeValues}
|
isChecked={shouldIncludeValues}
|
||||||
onCheckedChange={(isChecked) => setShouldIncludeValues(isChecked as boolean)}
|
onCheckedChange={(isChecked) => setShouldIncludeValues(isChecked as boolean)}
|
||||||
>
|
>
|
||||||
Include secret values
|
Include secret values
|
||||||
</Checkbox>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<Button
|
<Button
|
||||||
@@ -285,7 +246,7 @@ export const CopySecretsFromBoard = ({
|
|||||||
type="submit"
|
type="submit"
|
||||||
isDisabled={!isDirty}
|
isDisabled={!isDirty}
|
||||||
>
|
>
|
||||||
Paste Secrets
|
Copy Secrets
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="plain" colorSchema="secondary" onClick={() => onToggle(false)}>
|
<Button variant="plain" colorSchema="secondary" onClick={() => onToggle(false)}>
|
||||||
Cancel
|
Cancel
|
||||||
|
Reference in New Issue
Block a user