Merge branch 'main' into feat/addHumanitecIntegration

This commit is contained in:
carlosmonastyrski
2025-03-13 10:55:40 -03:00
103 changed files with 3196 additions and 567 deletions
@@ -228,7 +228,8 @@ export const OverviewPage = () => {
setPage
});
const { folderNamesAndDescriptions, getFolderByNameAndEnv, isFolderPresentInEnv } = useFolderOverview(folders);
const { folderNamesAndDescriptions, getFolderByNameAndEnv, isFolderPresentInEnv } =
useFolderOverview(folders);
const { dynamicSecretNames, isDynamicSecretPresentInEnv } =
useDynamicSecretOverview(dynamicSecrets);
@@ -251,7 +252,7 @@ export const OverviewPage = () => {
"updateFolder"
] as const);
const handleFolderCreate = async (folderName: string, description: string | null) => {
const handleFolderCreate = async (folderName: string, description: string | null) => {
const promises = userAvailableEnvs.map((env) => {
const environment = env.slug;
return createFolder({
@@ -1029,7 +1030,7 @@ export const OverviewPage = () => {
)}
{!isOverviewLoading && visibleEnvs.length > 0 && (
<>
{folderNamesAndDescriptions.map(({name: folderName, description}, index) => (
{folderNamesAndDescriptions.map(({ name: folderName, description }, index) => (
<SecretOverviewFolderRow
folderName={folderName}
isFolderPresentInEnv={isFolderPresentInEnv}
@@ -1161,7 +1162,9 @@ export const OverviewPage = () => {
<FolderForm
isEdit
defaultFolderName={(popUp.updateFolder?.data as Pick<TSecretFolder, "name">)?.name}
defaultDescription={(popUp.updateFolder?.data as Pick<TSecretFolder, "description">)?.description}
defaultDescription={
(popUp.updateFolder?.data as Pick<TSecretFolder, "description">)?.description
}
onUpdateFolder={handleFolderUpdate}
showDescriptionOverwriteWarning
/>
@@ -16,10 +16,10 @@ import {
useProjectPermission,
useWorkspace
} from "@app/context";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import { getKeyValue } from "@app/helpers/parseEnvVar";
import { useCreateFolder, useCreateSecretV3, useCreateWsTag, useGetWsTags } from "@app/hooks/api";
import { SecretType } from "@app/hooks/api/types";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
const typeSchema = z
.object({
@@ -23,6 +23,27 @@ import { useWorkspace } from "@app/context";
import { gatewaysQueryKeys, useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders, SqlProviders } from "@app/hooks/api/dynamicSecret/types";
const passwordRequirementsSchema = z
.object({
length: z.number().min(1).max(250),
required: z
.object({
lowercase: z.number().min(0),
uppercase: z.number().min(0),
digits: z.number().min(0),
symbols: z.number().min(0)
})
.refine((data) => {
const total = Object.values(data).reduce((sum, count) => sum + count, 0);
return total <= 250;
}, "Sum of required characters cannot exceed 250"),
allowedSymbols: z.string().optional()
})
.refine((data) => {
const total = Object.values(data.required).reduce((sum, count) => sum + count, 0);
return total <= data.length;
}, "Sum of required characters cannot exceed the total length");
const formSchema = z.object({
provider: z.object({
client: z.nativeEnum(SqlProviders),
@@ -31,6 +52,7 @@ const formSchema = z.object({
database: z.string().min(1),
username: z.string().min(1),
password: z.string().min(1),
passwordRequirements: passwordRequirementsSchema.optional(),
creationStatement: z.string().min(1),
revocationStatement: z.string().min(1),
renewStatement: z.string().optional(),
@@ -133,11 +155,24 @@ export const SqlDatabaseInputForm = ({
control,
setValue,
formState: { isSubmitting },
handleSubmit
handleSubmit,
watch
} = useForm<TForm>({
resolver: zodResolver(formSchema),
defaultValues: {
provider: getSqlStatements(SqlProviders.Postgres)
provider: {
...getSqlStatements(SqlProviders.Postgres),
passwordRequirements: {
length: 48,
required: {
lowercase: 1,
uppercase: 1,
digits: 1,
symbols: 0
},
allowedSymbols: "-_.~!*"
}
}
}
});
@@ -174,6 +209,10 @@ export const SqlDatabaseInputForm = ({
setValue("provider.renewStatement", sqlStatment.renewStatement);
setValue("provider.revocationStatement", sqlStatment.revocationStatement);
setValue("provider.port", getDefaultPort(type));
// Update password requirements based on provider
const length = type === SqlProviders.Oracle ? 30 : 48;
setValue("provider.passwordRequirements.length", length);
};
return (
@@ -197,6 +236,7 @@ export const SqlDatabaseInputForm = ({
)}
/>
</div>
<div className="w-32">
<Controller
control={control}
@@ -386,10 +426,15 @@ export const SqlDatabaseInputForm = ({
</FormControl>
)}
/>
<Accordion type="single" collapsible className="mb-2 w-full bg-mineshaft-700">
<AccordionItem value="advance-statements">
<AccordionTrigger>Modify SQL Statements</AccordionTrigger>
<Accordion type="multiple" className="mb-2 w-full bg-mineshaft-700">
<AccordionItem value="advanced">
<AccordionTrigger>
Creation, Revocation & Renew Statements (optional)
</AccordionTrigger>
<AccordionContent>
<div className="mb-4 text-sm text-mineshaft-300">
Customize SQL statements for managing database user lifecycle
</div>
<Controller
control={control}
name="provider.creationStatement"
@@ -450,6 +495,160 @@ export const SqlDatabaseInputForm = ({
</AccordionContent>
</AccordionItem>
</Accordion>
<Accordion type="multiple" className="mb-2 mt-4 w-full bg-mineshaft-700">
<AccordionItem value="password-config">
<AccordionTrigger>Password Configuration (optional)</AccordionTrigger>
<AccordionContent>
<div className="mb-4 text-sm text-mineshaft-300">
Set constraints on the generated database password
</div>
<div className="space-y-4">
<div>
<Controller
control={control}
name="provider.passwordRequirements.length"
defaultValue={48}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Password Length"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
type="number"
min={1}
max={250}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
</div>
<div className="space-y-2">
<h4 className="text-sm font-medium">Minimum Required Character Counts</h4>
<div className="text-sm text-gray-500">
{(() => {
const total = Object.values(
watch("provider.passwordRequirements.required") || {}
).reduce((sum, count) => sum + Number(count || 0), 0);
const length = watch("provider.passwordRequirements.length") || 0;
const isError = total > length;
return (
<span className={isError ? "text-red-500" : ""}>
Total required characters: {total}{" "}
{isError ? `(exceeds length of ${length})` : ""}
</span>
);
})()}
</div>
<div className="grid grid-cols-2 gap-4">
<Controller
control={control}
name="provider.passwordRequirements.required.lowercase"
defaultValue={1}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Lowercase Count"
isError={Boolean(error)}
errorText={error?.message}
helperText="Minimum number of lowercase letters"
>
<Input
type="number"
min={0}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="provider.passwordRequirements.required.uppercase"
defaultValue={1}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Uppercase Count"
isError={Boolean(error)}
errorText={error?.message}
helperText="Minimum number of uppercase letters"
>
<Input
type="number"
min={0}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="provider.passwordRequirements.required.digits"
defaultValue={1}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Digit Count"
isError={Boolean(error)}
errorText={error?.message}
helperText="Minimum number of digits"
>
<Input
type="number"
min={0}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="provider.passwordRequirements.required.symbols"
defaultValue={0}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Symbol Count"
isError={Boolean(error)}
errorText={error?.message}
helperText="Minimum number of symbols"
>
<Input
type="number"
min={0}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
</div>
</div>
<div className="space-y-2">
<h4 className="text-sm font-medium">Allowed Symbols</h4>
<Controller
control={control}
name="provider.passwordRequirements.allowedSymbols"
defaultValue="-_.~!*"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Symbols to use in password"
isError={Boolean(error)}
errorText={error?.message}
helperText="Default: -_.~!*"
>
<Input {...field} placeholder="-_.~!*" />
</FormControl>
)}
/>
</div>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</div>
</div>
@@ -15,7 +15,8 @@ type Props = {
showDescriptionOverwriteWarning?: boolean;
};
const descriptionOverwriteWarningMessage = "Warning: Any changes made here will overwrite any custom edits in individual environment folders."
const descriptionOverwriteWarningMessage =
"Warning: Any changes made here will overwrite any custom edits in individual environment folders.";
const formSchema = z.object({
name: z
@@ -25,9 +26,7 @@ const formSchema = z.object({
/^[a-zA-Z0-9-_]+$/,
"Folder name can only contain letters, numbers, dashes, and underscores"
),
description: z
.string()
.optional()
description: z.string().optional()
});
type TFormData = z.infer<typeof formSchema>;
@@ -59,7 +58,7 @@ export const FolderForm = ({
if (textarea) {
const lines = textarea.value.split("\n");
const maxDescriptionLines = 10;
if (lines.length > maxDescriptionLines) {
textarea.value = lines.slice(0, maxDescriptionLines).join("\n");
}
@@ -90,30 +89,32 @@ export const FolderForm = ({
)}
/>
<Controller
control={control}
name="description"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Folder Description"
isError={Boolean(error)}
tooltipText={showDescriptionOverwriteWarning ? descriptionOverwriteWarningMessage : undefined}
isOptional
errorText={error?.message}
className="flex-1"
>
<TextArea
placeholder="Folder description"
{...field}
rows={3}
ref={descriptionRef}
onInput={handleInput}
className="thin-scrollbar w-full !resize-none bg-mineshaft-900"
maxLength={255}
/>
</FormControl>
)}
/>
control={control}
name="description"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Folder Description"
isError={Boolean(error)}
tooltipText={
showDescriptionOverwriteWarning ? descriptionOverwriteWarningMessage : undefined
}
isOptional
errorText={error?.message}
className="flex-1"
>
<TextArea
placeholder="Folder description"
{...field}
rows={3}
ref={descriptionRef}
onInput={handleInput}
className="thin-scrollbar w-full !resize-none bg-mineshaft-900"
maxLength={255}
/>
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button className="mr-4" type="submit" isDisabled={isSubmitting} isLoading={isSubmitting}>
{isEdit ? "Save" : "Create"}
@@ -23,6 +23,27 @@ import { useWorkspace } from "@app/context";
import { gatewaysQueryKeys, useUpdateDynamicSecret } from "@app/hooks/api";
import { SqlProviders, TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
const passwordRequirementsSchema = z
.object({
length: z.number().min(1).max(250),
required: z
.object({
lowercase: z.number().min(0),
uppercase: z.number().min(0),
digits: z.number().min(0),
symbols: z.number().min(0)
})
.refine((data) => {
const total = Object.values(data).reduce((sum, count) => sum + count, 0);
return total <= 250; // Sanity check for individual validation
}, "Sum of required characters cannot exceed 250"),
allowedSymbols: z.string().optional()
})
.refine((data) => {
const total = Object.values(data.required).reduce((sum, count) => sum + count, 0);
return total <= data.length;
}, "Sum of required characters cannot exceed the total length");
const formSchema = z.object({
inputs: z
.object({
@@ -32,6 +53,7 @@ const formSchema = z.object({
database: z.string().min(1),
username: z.string().min(1),
password: z.string().min(1),
passwordRequirements: passwordRequirementsSchema.optional(),
creationStatement: z.string().min(1),
revocationStatement: z.string().min(1),
renewStatement: z.string().optional(),
@@ -82,6 +104,17 @@ export const EditDynamicSecretSqlProviderForm = ({
secretPath,
projectSlug
}: Props) => {
const getDefaultPasswordRequirements = (provider: SqlProviders) => ({
length: provider === SqlProviders.Oracle ? 30 : 48,
required: {
lowercase: 1,
uppercase: 1,
digits: 1,
symbols: 0
},
allowedSymbols: "-_.~!*"
});
const {
control,
watch,
@@ -94,10 +127,16 @@ export const EditDynamicSecretSqlProviderForm = ({
maxTTL: dynamicSecret.maxTTL,
newName: dynamicSecret.name,
inputs: {
...(dynamicSecret.inputs as TForm["inputs"])
...(dynamicSecret.inputs as TForm["inputs"]),
passwordRequirements:
(dynamicSecret.inputs as TForm["inputs"])?.passwordRequirements ||
getDefaultPasswordRequirements(
(dynamicSecret.inputs as TForm["inputs"])?.client || SqlProviders.Postgres
)
}
}
});
const { currentWorkspace } = useWorkspace();
const { data: projectGateways, isPending: isProjectGatewaysLoading } = useQuery(
gatewaysQueryKeys.listProjectGateways({ projectId: currentWorkspace.id })
@@ -347,10 +386,15 @@ export const EditDynamicSecretSqlProviderForm = ({
</FormControl>
)}
/>
<Accordion type="multiple" className="w-full bg-mineshaft-700">
<AccordionItem value="modify-sql-statement">
<AccordionTrigger>Modify SQL Statements</AccordionTrigger>
<Accordion type="multiple" className="mb-2 mt-4 w-full bg-mineshaft-700">
<AccordionItem value="advanced">
<AccordionTrigger>
Creation, Revocation & Renew Statements (optional)
</AccordionTrigger>
<AccordionContent>
<div className="mb-4 text-sm text-mineshaft-300">
Customize SQL statements for managing database user lifecycle
</div>
<Controller
control={control}
name="inputs.creationStatement"
@@ -418,6 +462,160 @@ export const EditDynamicSecretSqlProviderForm = ({
</AccordionContent>
</AccordionItem>
</Accordion>
<Accordion type="multiple" className="mb-2 mt-4 w-full bg-mineshaft-700">
<AccordionItem value="password-config">
<AccordionTrigger>Password Configuration (optional)</AccordionTrigger>
<AccordionContent>
<div className="mb-4 text-sm text-mineshaft-300">
Set constraints on the generated database password
</div>
<div className="space-y-4">
<div>
<Controller
control={control}
name="inputs.passwordRequirements.length"
defaultValue={48}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Password Length"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
type="number"
min={1}
max={250}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
</div>
<div className="space-y-2">
<h4 className="text-sm font-medium">Minimum Required Character Counts</h4>
<div className="text-sm text-gray-500">
{(() => {
const total = Object.values(
watch("inputs.passwordRequirements.required") || {}
).reduce((sum, count) => sum + Number(count || 0), 0);
const length = watch("inputs.passwordRequirements.length") || 0;
const isError = total > length;
return (
<span className={isError ? "text-red-500" : ""}>
Total required characters: {total}{" "}
{isError ? `(exceeds length of ${length})` : ""}
</span>
);
})()}
</div>
<div className="grid grid-cols-2 gap-4">
<Controller
control={control}
name="inputs.passwordRequirements.required.lowercase"
defaultValue={1}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Lowercase Count"
isError={Boolean(error)}
errorText={error?.message}
helperText="Minimum number of lowercase letters"
>
<Input
type="number"
min={0}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.passwordRequirements.required.uppercase"
defaultValue={1}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Uppercase Count"
isError={Boolean(error)}
errorText={error?.message}
helperText="Minimum number of uppercase letters"
>
<Input
type="number"
min={0}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.passwordRequirements.required.digits"
defaultValue={1}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Digit Count"
isError={Boolean(error)}
errorText={error?.message}
helperText="Minimum number of digits"
>
<Input
type="number"
min={0}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.passwordRequirements.required.symbols"
defaultValue={0}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Symbol Count"
isError={Boolean(error)}
errorText={error?.message}
helperText="Minimum number of symbols"
>
<Input
type="number"
min={0}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
</div>
</div>
<div className="space-y-2">
<h4 className="text-sm font-medium">Allowed Symbols</h4>
<Controller
control={control}
name="inputs.passwordRequirements.allowedSymbols"
defaultValue="-_.~!*"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Symbols to use in password"
isError={Boolean(error)}
errorText={error?.message}
helperText="Default: -_.~!*"
>
<Input {...field} placeholder="-_.~!*" />
</FormControl>
)}
/>
</div>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</div>
</div>
@@ -1,17 +1,17 @@
import { subject } from "@casl/ability";
import { faClose, faFolder, faPencilSquare, faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { faClose, faFolder, faInfoCircle, faPencilSquare } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useSearch } from "@tanstack/react-router";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { DeleteActionModal, IconButton, Modal, ModalContent } from "@app/components/v2";
import { Tooltip } from "@app/components/v2/Tooltip/Tooltip";
import { ROUTE_PATHS } from "@app/const/routes";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useDeleteFolder, useUpdateFolder } from "@app/hooks/api";
import { TSecretFolder } from "@app/hooks/api/secretFolders/types";
import { Tooltip } from "@app/components/v2/Tooltip/Tooltip";
import { FolderForm } from "../ActionBar/FolderForm";
@@ -118,16 +118,15 @@ export const FolderListView = ({
onClick={() => handleFolderClick(name)}
>
{name}
{
description &&
{description && (
<Tooltip
position="right"
className="flex items-center space-x-4 max-w-lg py-4 whitespace-pre-wrap"
className="flex max-w-lg items-center space-x-4 whitespace-pre-wrap py-4"
content={description}
>
<FontAwesomeIcon icon={faInfoCircle} className="text-mineshaft-400 ml-1" />
<FontAwesomeIcon icon={faInfoCircle} className="ml-1 text-mineshaft-400" />
</Tooltip>
}
)}
</div>
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3">
<ProjectPermissionCan
@@ -62,7 +62,7 @@ export const PitDrawer = ({
<div>
{(() => {
const distance = formatDistance(new Date(createdAt), new Date());
return distance.charAt(0).toUpperCase() + distance.slice(1) + " ago";
return `${distance.charAt(0).toUpperCase() + distance.slice(1)} ago`;
})()}
</div>
<div>{getButtonLabel(i === 0 && index === 0, snapshotId === id)}</div>
@@ -58,10 +58,10 @@ import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries";
import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
import { camelCaseToSpaces } from "@app/lib/fn/string";
import { CreateReminderForm } from "./CreateReminderForm";
import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils";
import { camelCaseToSpaces } from "@app/lib/fn/string";
type Props = {
isOpen?: boolean;
@@ -281,7 +281,7 @@ export const SecretItem = memo(
isVisible={isVisible}
isReadOnly={isReadOnly}
{...field}
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
containerClassName="py-1.5 rounded-md transition-all"
/>
)}
/>
@@ -301,19 +301,19 @@ export const SecretItem = memo(
secretPath={secretPath}
{...field}
defaultValue={secretValueHidden ? "" : undefined}
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
containerClassName="py-1.5 rounded-md transition-all"
/>
)}
/>
)}
<div key="actions" className="flex h-8 flex-shrink-0 self-start transition-all">
<div key="actions" className="flex h-full flex-shrink-0 self-start transition-all group-hover:gap-x-2">
<Tooltip content="Copy secret">
<IconButton
isDisabled={secret.secretValueHidden}
ariaLabel="copy-value"
variant="plain"
size="sm"
className="w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5"
className="w-0 overflow-hidden p-0 group-hover:w-5"
onClick={copyTokenToClipboard}
>
<FontAwesomeSymbol
@@ -339,7 +339,7 @@ export const SecretItem = memo(
<Modal>
<ModalTrigger asChild>
<IconButton
className="w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6"
className="w-0 overflow-hidden p-0 group-hover:w-5"
variant="plain"
size="md"
ariaLabel="reference-tree"
@@ -390,7 +390,7 @@ export const SecretItem = memo(
variant="plain"
size="sm"
className={twMerge(
"w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-5",
"w-0 overflow-hidden p-0 group-hover:w-5 data-[state=open]:w-5",
hasTagsApplied && "w-5 text-primary"
)}
isDisabled={!isAllowed}
@@ -473,7 +473,7 @@ export const SecretItem = memo(
size="sm"
onClick={handleOverrideClick}
className={twMerge(
"w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5",
"w-0 overflow-hidden p-0 group-hover:w-5",
isOverriden && "w-5 text-primary"
)}
>
@@ -498,7 +498,7 @@ export const SecretItem = memo(
<PopoverTrigger asChild disabled={!isAllowed}>
<IconButton
className={twMerge(
"w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6",
"w-0 overflow-hidden p-0 group-hover:w-5",
hasComment && "w-5 text-primary"
)}
variant="plain"
@@ -518,7 +518,7 @@ export const SecretItem = memo(
</ProjectPermissionCan>
<IconButton
isDisabled={secret.secretValueHidden}
className="w-0 overflow-hidden p-0 group-hover:mr-2 group-hover:w-5 data-[state=open]:w-6"
className="w-0 overflow-hidden p-0 group-hover:w-5"
variant="plain"
size="md"
ariaLabel="share-secret"
@@ -206,8 +206,13 @@ export const DatabricksConfigurePage = () => {
<span className="text-md ml-3 text-mineshaft-100">Pro Tip</span>
</div>
<span className="mt-4 text-sm text-mineshaft-300">
After creating an integration, your secrets will start syncing immediately. This might
cause an unexpected override of current secrets in Databricks with secrets from Infisical.
When integrating with Databricks, Infisical is intended to be the source of truth for the
secrets in the configured Databricks scope.
</span>
<span className="mt-4 text-sm text-mineshaft-300">
Any secrets not present in Infisical will be removed from the specified scope. To prevent
removal of secrets not managed by Infisical, Infisical recommends creating a designated
secret scope for your integration.
</span>
</div>
</div>