merge main

This commit is contained in:
x032205
2025-10-10 20:33:52 -04:00
parent a61536adbb
commit a804672042
1020 changed files with 23364 additions and 13891 deletions
@@ -26,8 +26,8 @@ export const AppConnectionOption = ({
<p className="truncate">{children}</p>
{!props.data.projectId && (
<Tooltip content="This connection belongs to your organization.">
<div className="ml-2 mr-auto">
<Badge className="flex h-5 w-min items-center gap-1 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300 hover:text-bunker-300">
<div className="mr-auto ml-2">
<Badge className="flex h-5 w-min items-center gap-1 bg-mineshaft-400/50 whitespace-nowrap text-bunker-300 hover:text-bunker-300">
<FontAwesomeIcon icon={faBuilding} size="sm" />
Organization
</Badge>
@@ -91,7 +91,7 @@ export default function CodeInputStep({
return (
<div className="mx-auto h-full w-full pb-4 md:px-8">
<p className="text-md flex justify-center text-bunker-200">{t("signup.step2-message")}</p>
<p className="text-md my-1 flex justify-center font-semibold text-bunker-200">{email} </p>
<p className="text-md my-1 flex justify-center font-medium text-bunker-200">{email} </p>
<div className="mx-auto hidden w-max min-w-[20rem] md:block">
<ReactCodeInput
name=""
@@ -100,7 +100,7 @@ export default function CodeInputStep({
fields={6}
onChange={setCode}
{...props}
className="mb-2 mt-6"
className="mt-6 mb-2"
/>
</div>
<div className="mx-auto mt-4 block w-max md:hidden">
@@ -111,11 +111,11 @@ export default function CodeInputStep({
fields={6}
onChange={setCode}
{...propsPhone}
className="mb-2 mt-2"
className="mt-2 mb-2"
/>
</div>
{codeError && <Error text={t("signup.step2-code-error")} />}
<div className="mx-auto mt-2 flex w-1/4 min-w-[20rem] max-w-xs flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-[19%]">
<div className="mx-auto mt-2 flex w-1/4 max-w-xs min-w-[20rem] flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-[19%]">
<div className="text-l w-full py-1 text-lg">
<Button
type="submit"
+11 -23
View File
@@ -1,9 +1,8 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "@tanstack/react-router";
import axios from "axios";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { useSendVerificationEmail } from "@app/hooks/api";
import { Button, Input } from "../v2";
@@ -35,11 +34,10 @@ export default function EnterEmailStep({
* Verifies if the entered email "looks" correct
*/
const emailCheck = async () => {
const isValid = z.string().email().safeParse(email);
let emailCheckBool = false;
if (!email) {
setEmailError(true);
emailCheckBool = true;
} else if (!email.includes("@") || !email.includes(".") || !/[a-z]/.test(email)) {
if (!isValid.success) {
setEmailError(true);
emailCheckBool = true;
} else {
@@ -48,26 +46,16 @@ export default function EnterEmailStep({
// If everything is correct, go to the next step
if (!emailCheckBool) {
try {
await mutateAsync({ email: email.toLowerCase() });
setEmail(email.toLowerCase());
incrementStep();
} catch (e) {
if (axios.isAxiosError(e)) {
const { message = "Something went wrong" } = e.response?.data as { message: string };
createNotification({
type: "error",
text: message
});
}
}
await mutateAsync({ email: email.toLowerCase() });
setEmail(email.toLowerCase());
incrementStep();
}
};
return (
<div>
<div className="mx-auto w-full md:px-6">
<p className="flex justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-xl font-medium text-transparent">
<p className="flex justify-center bg-linear-to-b from-white to-bunker-200 bg-clip-text text-xl font-medium text-transparent">
{t("signup.step1-start")}
</p>
<div className="m-auto mt-8 flex w-1/4 min-w-[20rem] flex-col items-center justify-center rounded-lg lg:w-1/6">
@@ -80,12 +68,12 @@ export default function EnterEmailStep({
className="h-12"
/>
{emailError && (
<p className="ml-1.5 mt-1.5 w-full text-left text-xs text-red-600">
<p className="mt-1.5 ml-1.5 w-full text-left text-xs text-red-600">
Please enter a valid email.
</p>
)}
</div>
<div className="mx-auto mt-2 flex w-1/4 min-w-[20rem] max-w-xs flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-1/6">
<div className="mx-auto mt-2 flex w-1/4 max-w-xs min-w-[20rem] flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-1/6">
<div className="text-l w-full py-1 text-lg">
<Button
type="submit"
@@ -104,7 +92,7 @@ export default function EnterEmailStep({
</div>
</div>
</div>
<div className="mx-auto mb-48 mt-2 flex w-full max-w-md flex-col items-center justify-center pt-2 md:mb-16 md:pb-2">
<div className="mx-auto mt-2 mb-48 flex w-full max-w-md flex-col items-center justify-center pt-2 md:mb-16 md:pb-2">
<Link to="/login">
<button type="button" className="w-max pb-3 duration-200 hover:opacity-90">
<span className="cursor-pointer text-sm text-mineshaft-400 duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4">
@@ -23,7 +23,7 @@ export default function InitialSignupStep({
return (
<div className="mx-auto flex w-full flex-col items-center justify-center">
<h1 className="mb-8 bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
<h1 className="mb-8 bg-linear-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
{t("signup.initial-title")}
</h1>
<RegionSelect />
+9 -9
View File
@@ -159,7 +159,7 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
<div className="mb-6 text-center text-lg font-bold text-white">
Your organization requires mobile authentication to be configured.
</div>
<div className="mx-auto w-max pb-4 pt-4 md:mb-16 md:px-8">
<div className="mx-auto w-max pt-4 pb-4 md:mb-16 md:px-8">
<TotpRegistration
shouldCenterQr
onComplete={async () => {
@@ -173,7 +173,7 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
}
return (
<div className="mx-auto w-max pb-6 pt-6 md:mb-16 md:px-8">
<div className="mx-auto w-max pt-6 pb-6 md:mb-16 md:px-8">
{!hideLogo && (
<Link to="/">
<div className="mb-4 flex justify-center">
@@ -184,12 +184,12 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
{method === MfaMethod.EMAIL && (
<>
<p className="text-l flex justify-center text-bunker-300">{t("mfa.step2-message")}</p>
<p className="text-l my-1 flex justify-center font-semibold text-bunker-300">{email}</p>
<p className="text-l my-1 flex justify-center font-medium text-bunker-300">{email}</p>
</>
)}
{method === MfaMethod.TOTP && (
<div className="mb-8 text-center">
<h2 className="mb-3 text-xl font-semibold text-bunker-100">Two-Factor Authentication</h2>
<h2 className="mb-3 text-xl font-medium text-bunker-100">Two-Factor Authentication</h2>
<p className="mx-auto max-w-md text-sm leading-relaxed text-bunker-300">
{showRecoveryCodeInput
? "Enter one of your backup recovery codes"
@@ -207,13 +207,13 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
type="text"
fields={6}
onChange={setMfaCode}
className="mb-2 mt-6"
className="mt-6 mb-2"
{...codeInputProps}
/>
</div>
)}
{method === MfaMethod.TOTP && (
<div className="mb-6 mt-8 flex justify-center">
<div className="mt-8 mb-6 flex justify-center">
<ReactCodeInput
key={showRecoveryCodeInput ? "recovery" : "totp"}
name=""
@@ -236,13 +236,13 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
type="text"
fields={6}
onChange={setMfaCode}
className="mb-2 mt-2"
className="mt-2 mb-2"
{...codeInputPropsPhone}
/>
</div>
)}
{method === MfaMethod.TOTP && (
<div className="mb-6 mt-4 flex justify-center">
<div className="mt-4 mb-6 flex justify-center">
<ReactCodeInput
key={showRecoveryCodeInput ? "recovery-mobile" : "totp-mobile"}
name=""
@@ -264,7 +264,7 @@ export const Mfa = ({ successCallback, closeMfa, hideLogo, email, method }: Prop
size="md"
type="submit"
isFullWidth
className="h-11 rounded-lg font-medium shadow-sm transition-all duration-200 hover:shadow-md"
className="h-11 rounded-lg font-medium shadow-xs transition-all duration-200 hover:shadow-md"
colorSchema="primary"
variant="outline_bg"
isLoading={isLoading}
@@ -41,26 +41,26 @@ export default function TeamInviteStep(): JSX.Element {
};
return (
<div className="min-w-lg mx-auto mb-64 h-full w-max px-8 pb-4 md:mb-32">
<p className="flex justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-2xl font-semibold text-transparent">
<div className="mx-auto mb-64 h-full w-max min-w-lg px-8 pb-4 md:mb-32">
<p className="flex justify-center bg-linear-to-b from-white to-bunker-200 bg-clip-text text-2xl font-medium text-transparent">
{t("signup.step5-invite-team")}
</p>
<p className="mb-6 mt-4 flex justify-center text-center text-bunker-400 md:mx-8">
<p className="mt-4 mb-6 flex justify-center text-center text-bunker-400 md:mx-8">
{t("signup.step5-subtitle")}
</p>
<div className="mx-auto mb-6 w-max rounded-xl border border-mineshaft-500 bg-mineshaft-800 px-8 pb-4 pt-6 drop-shadow-xl">
<div className="mx-auto mb-6 w-max rounded-xl border border-mineshaft-500 bg-mineshaft-800 px-8 pt-6 pb-4 drop-shadow-xl">
<div>
<div className="pb-1 pl-1 text-sm font-medium text-bunker-300">
<span>Emails</span>
</div>
<textarea
className="h-20 w-full min-w-[30rem] rounded-md border border-mineshaft-500 bg-mineshaft-900/70 px-2 py-1 text-sm text-bunker-300 outline-none ring-primary-800 ring-opacity-70 placeholder:text-bunker-400 focus:ring-2"
className="ring-opacity-70 h-20 w-full min-w-120 rounded-md border border-mineshaft-500 bg-mineshaft-900/70 px-2 py-1 text-sm text-bunker-300 ring-primary-800 outline-hidden placeholder:text-bunker-400 focus:ring-2"
value={emails}
onChange={(e) => setEmails(e.target.value)}
placeholder="[email protected], [email protected]..."
/>
</div>
<div className="mx-auto mt-2 flex w-full flex-row items-end justify-end text-sm md:mb-2 md:mt-4 md:min-w-[30rem] md:max-w-md">
<div className="mx-auto mt-2 flex w-full flex-row items-end justify-end text-sm md:mt-4 md:mb-2 md:max-w-md md:min-w-120">
<Button
onClick={() => {
if (serverDetails?.emailConfigured) {
@@ -84,7 +84,7 @@ export default function TeamInviteStep(): JSX.Element {
onOpenChange={(isOpen) => handlePopUpToggle("setUpEmail", isOpen)}
/>
</div>
<div className="mx-auto mb-2 mt-4 flex max-h-24 min-w-[20rem] max-w-max flex-row items-center justify-center px-4 text-lg md:p-2">
<div className="mx-auto mt-4 mb-2 flex max-h-24 max-w-max min-w-[20rem] flex-row items-center justify-center px-4 text-lg md:p-2">
<Button
onClick={redirectToHome}
size="sm"
@@ -139,7 +139,7 @@ export default function UserInfoStep({
return (
<div className="mx-auto mb-36 h-full w-max rounded-xl md:mb-16 md:px-8">
<p className="text-medium mx-8 mb-6 flex justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-xl font-bold text-transparent md:mx-16">
<p className="text-medium mx-8 mb-6 flex justify-center bg-linear-to-b from-white to-bunker-200 bg-clip-text text-xl font-bold text-transparent md:mx-16">
{t("signup.step3-message")}
</p>
<div className="mx-auto mb-36 h-full w-max rounded-xl py-6 md:mb-16 md:border md:border-mineshaft-600 md:bg-mineshaft-800 md:px-8">
@@ -156,7 +156,7 @@ export default function UserInfoStep({
className="h-12"
/>
{nameError && (
<p className="ml-1 mt-1 w-full text-left text-xs text-red-600">
<p className="mt-1 ml-1 w-full text-left text-xs text-red-600">
Please, specify your name
</p>
)}
@@ -174,7 +174,7 @@ export default function UserInfoStep({
className="h-12"
/>
{organizationNameError && (
<p className="ml-1 mt-1 w-full text-left text-xs text-red-600">
<p className="mt-1 ml-1 w-full text-left text-xs text-red-600">
Please, specify your organization name
</p>
)}
@@ -219,7 +219,7 @@ export default function UserInfoStep({
<div>
<FontAwesomeIcon
icon={faXmark}
className="text-md ml-0.5 mr-2.5 text-red"
className="text-md mr-2.5 ml-0.5 text-red"
/>
</div>
<p className="text-sm text-gray-400">{errors[key as keyof Errors]}</p>
@@ -232,7 +232,7 @@ export default function UserInfoStep({
</div>
)}
</div>
<div className="mx-auto mt-2 flex w-1/4 min-w-[20rem] max-w-xs flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-[19%]">
<div className="mx-auto mt-2 flex w-1/4 max-w-xs min-w-[20rem] flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-[19%]">
<div className="text-l w-full py-1 text-lg">
<Button
type="submit"
+1 -1
View File
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const Error = ({ text }: { text: string }): JSX.Element => {
return (
<div className="relative m-auto flex w-fit flex-row items-center justify-center rounded-full">
<FontAwesomeIcon icon={faExclamationTriangle} className="mx-2 mb-2 mt-1.5 text-red" />
<FontAwesomeIcon icon={faExclamationTriangle} className="mx-2 mt-1.5 mb-2 text-red" />
{text && <p className="relative top-0 mr-2 py-1 text-sm text-red">{text}</p>}
</div>
);
+8 -8
View File
@@ -40,7 +40,7 @@ const InputField = ({
if (isStatic === true) {
return (
<div className="my-2 flex w-full max-w-md flex-col justify-center md:my-4">
<p className="mb-0.5 text-sm font-semibold text-gray-400">{label}</p>
<p className="mb-0.5 text-sm font-medium text-gray-400">{label}</p>
{text && <p className="mb-2 text-xs text-gray-400">{text}</p>}
<input
onChange={(e) => onChangeHandler(e.target.value)}
@@ -48,7 +48,7 @@ const InputField = ({
placeholder={placeholder}
value={value}
required={isRequired}
className="text-md w-full min-w-16 rounded-md border border-gray-600 bg-bunker-800 p-2 text-gray-400 outline-none"
className="text-md w-full min-w-16 rounded-md border border-gray-600 bg-bunker-800 p-2 text-gray-400 outline-hidden"
name={name}
readOnly
autoComplete={autoComplete}
@@ -60,7 +60,7 @@ const InputField = ({
return (
<div className="w-full flex-col">
<div className="mb-0.5 flex flex-row items-center text-mineshaft-300">
<p className="mr-1 text-sm font-semibold">{label}</p>
<p className="mr-1 text-sm font-medium">{label}</p>
</div>
<div
className={`group relative flex w-full max-w-2xl flex-col justify-center border ${
@@ -75,11 +75,11 @@ const InputField = ({
required={isRequired}
className={`${
blurred
? "text-bunker-800 focus:text-gray-400 active:text-gray-400 group-hover:text-gray-400"
? "text-bunker-800 group-hover:text-gray-400 focus:text-gray-400 active:text-gray-400"
: ""
} ${
error ? "focus:ring-red/50" : "focus:ring-primary/50"
} text-md peer relative w-full min-w-16 rounded-md bg-mineshaft-900 p-2 text-gray-400 outline-none duration-200 focus:ring-4`}
} text-md peer relative w-full min-w-16 rounded-md bg-mineshaft-900 p-2 text-gray-400 outline-hidden duration-200 focus:ring-4`}
name={name}
spellCheck="false"
autoComplete={autoComplete}
@@ -101,7 +101,7 @@ const InputField = ({
</button>
)}
{blurred && (
<div className="peer absolute flex h-10 w-fit max-w-xl items-center overflow-hidden text-clip rounded-md text-gray-400/50 group-hover:hidden peer-hover:hidden peer-focus:hidden peer-active:invisible">
<div className="peer absolute flex h-10 w-fit max-w-xl items-center overflow-hidden rounded-md text-clip text-gray-400/50 group-hover:!hidden peer-hover:hidden peer-focus:hidden peer-active:invisible">
<p className="ml-2" />
{value
.split("")
@@ -109,7 +109,7 @@ const InputField = ({
.map(() => (
<FontAwesomeIcon
key={guidGenerator()}
className="mx-0.5 text-xxs"
className="text-xxs mx-0.5"
icon={faCircle}
/>
))}
@@ -121,7 +121,7 @@ const InputField = ({
</div>
)} */}
</div>
{error && <p className="mx-0 mb-2 mt-0.5 max-w-xs text-xs text-red">{errorText}</p>}
{error && <p className="mx-0 mt-0.5 mb-2 max-w-xs text-xs text-red">{errorText}</p>}
</div>
);
};
@@ -12,10 +12,8 @@ export const NoEnvironmentsBanner = ({ projectId }: IProps) => {
return (
<div className="mt-4 flex w-full flex-row items-center rounded-md border border-primary-600/70 bg-primary/[.07] p-4 text-base text-white">
<div className="flex w-full flex-col text-sm">
<span className="mb-2 text-lg font-semibold">
No environments in your project was found
</span>
<p className="prose">
<span className="mb-2 text-lg font-medium">No environments in your project was found</span>
<p>
In order to use integrations, you need to create at least one environment in your project.
</p>
</div>
@@ -58,7 +58,7 @@ export const RecoveryCodesDownload = ({
<Modal isOpen={isOpen} onOpenChange={hasDownloaded ? handleClose : () => {}}>
<ModalContent title="Recovery Codes" className="max-w-md">
<div className="space-y-4">
<div className="rounded border border-yellow bg-yellow/10 p-2 px-3 text-xs text-yellow">
<div className="rounded-sm border border-yellow bg-yellow/10 p-2 px-3 text-xs text-yellow">
Save these codes securely. Each can only be used once.
</div>
@@ -66,35 +66,33 @@ export default function NavHeader({
return (
<div className="flex flex-row items-center pt-6">
<div className="mr-2 flex h-5 w-5 min-w-[1.25rem] items-center justify-center rounded-md bg-primary text-sm text-black">
<div className="mr-2 flex h-5 w-5 min-w-5 items-center justify-center rounded-md bg-primary text-sm text-black">
{currentOrg?.name?.charAt(0)}
</div>
<Link
to="/organization/projects"
className="truncate pl-0.5 text-sm font-semibold text-primary/80 hover:text-primary"
className="truncate pl-0.5 text-sm font-medium text-primary/80 hover:text-primary"
>
{currentOrg?.name}
</Link>
{isProjectRelated && (
<>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-xs text-gray-400" />
<div className="truncate text-sm font-semibold text-bunker-300">
{currentProject?.name}
</div>
<FontAwesomeIcon icon={faAngleRight} className="mr-3 ml-3 text-xs text-gray-400" />
<div className="truncate text-sm font-medium text-bunker-300">{currentProject?.name}</div>
</>
)}
{isOrganizationRelated && (
<>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-xs text-gray-400" />
<div className="text-sm font-semibold text-bunker-300">Organization Settings</div>
<FontAwesomeIcon icon={faAngleRight} className="mr-3 ml-3 text-xs text-gray-400" />
<div className="text-sm font-medium text-bunker-300">Organization Settings</div>
</>
)}
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
<FontAwesomeIcon icon={faAngleRight} className="mr-3 ml-3 text-sm text-gray-400" />
{pageName === "Secrets" ? (
<Link
to="/projects/secret-management/$projectId/overview"
params={{ projectId: currentProject.id }}
className="text-sm font-semibold text-primary/80 hover:text-primary"
className="text-sm font-medium text-primary/80 hover:text-primary"
>
{pageName}
</Link>
@@ -103,7 +101,7 @@ export default function NavHeader({
)}
{currentEnv && secretPath === "/" && (
<>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-xs text-gray-400" />
<FontAwesomeIcon icon={faAngleRight} className="mr-1.5 ml-3 text-xs text-gray-400" />
<div className="rounded-md pl-3 hover:bg-bunker-100/10">
<Tooltip content="Select environment">
<Select
@@ -126,11 +124,11 @@ export default function NavHeader({
)}
{isFolderMode && routerEnvSlug && Boolean(secretPathSegments.length) && (
<div className="flex items-center space-x-3">
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-xs text-gray-400" />
<FontAwesomeIcon icon={faAngleRight} className="mr-1.5 ml-3 text-xs text-gray-400" />
<Link
to="/projects/secret-management/$projectId/secrets/$envSlug"
params={{ projectId: currentProject.id, envSlug: routerEnvSlug }}
className="text-sm font-semibold text-primary/80 hover:text-primary"
className="text-sm font-medium text-primary/80 hover:text-primary"
>
{userAvailableEnvs?.find(({ slug }) => slug === currentEnv)?.name}
</Link>
@@ -145,7 +143,7 @@ export default function NavHeader({
className="flex items-center space-x-3"
key={`breadcrumb-secret-path-${folderName}`}
>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-1.5 text-xs text-gray-400" />
<FontAwesomeIcon icon={faAngleRight} className="mr-1.5 ml-3 text-xs text-gray-400" />
{index + 1 === secretPathSegments?.length ? (
<div className="flex items-center space-x-2">
<span
@@ -122,7 +122,7 @@ export const RegionSelect = () => {
<span className="mr-2 inline-block w-4">{flag}</span>
{value.toUpperCase()} Region
</p>
<ul className="ml-6 mt-2 flex flex-col gap-1">
<ul className="mt-2 ml-6 flex flex-col gap-1">
<li>
<FontAwesomeIcon size="xs" className="mr-0.5 text-green" icon={faCheck} /> Fastest
option if you are based in {value === Region.US ? "the" : ""} {label}
@@ -57,7 +57,7 @@ export const SecretDashboardPathBreadcrumb = ({
type: "info"
});
}}
className="opacity-0 transition duration-75 hover:bg-bunker-100/10 group-hover:opacity-100"
className="opacity-0 transition duration-75 group-hover:opacity-100 hover:bg-bunker-100/10"
>
<FontAwesomeIcon
icon={!isCopying ? faCopy : faCheck}
@@ -13,7 +13,7 @@ export const LastLoginSection = ({ lastLoginTime, lastLoginAuthMethod }: Props)
<div className="font-medium">Last Login</div>
</div>
<div className="mb-2 flex items-center gap-2 text-sm">
<div className="flex items-center justify-center rounded bg-mineshaft-700 p-3">
<div className="flex items-center justify-center rounded-sm bg-mineshaft-700 p-3">
<FontAwesomeIcon icon={faShield} className="h-4 w-4" />
</div>
<div className="flex flex-col">
@@ -22,7 +22,7 @@ export const LastLoginSection = ({ lastLoginTime, lastLoginAuthMethod }: Props)
</div>
</div>
<div className="flex items-center gap-2 text-sm">
<div className="flex items-center justify-center rounded bg-mineshaft-700 p-3">
<div className="flex items-center justify-center rounded-sm bg-mineshaft-700 p-3">
<FontAwesomeIcon icon={faClock} className="h-4 w-4" />
</div>
<div className="flex flex-col">
@@ -107,7 +107,7 @@ const AccessTreeContent = ({ permissions, subject, onClose }: AccessTreeProps) =
"mt-4 w-full",
viewMode === ViewMode.Modal && "fixed inset-0 z-50 p-10",
viewMode === ViewMode.Undocked &&
"fixed bottom-4 left-20 z-50 h-[40%] w-[38%] min-w-[32rem] lg:w-[34%]"
"fixed bottom-4 left-20 z-50 h-[40%] w-[38%] min-w-lg lg:w-[34%]"
)}
>
<div
@@ -119,7 +119,7 @@ const AccessTreeContent = ({ permissions, subject, onClose }: AccessTreeProps) =
{viewMode === ViewMode.Docked && (
<div className="mb-4 flex items-start justify-between border-b border-mineshaft-400 pb-4">
<div>
<h3 className="text-lg font-semibold text-mineshaft-100">Access Tree</h3>
<h3 className="text-lg font-medium text-mineshaft-100">Access Tree</h3>
<p className="text-sm leading-3 text-mineshaft-400">
Visual access policies for the configured role.
</p>
@@ -191,7 +191,7 @@ const AccessTreeContent = ({ permissions, subject, onClose }: AccessTreeProps) =
<SelectItem
key={env.slug}
value={env.slug}
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
className="relative py-2 pr-8 pl-6 text-sm hover:bg-mineshaft-700"
>
<div className="ml-3 truncate font-medium">{env.name}</div>
</SelectItem>
@@ -209,7 +209,7 @@ const AccessTreeContent = ({ permissions, subject, onClose }: AccessTreeProps) =
<Panel position="top-right" className="flex gap-2">
<Tooltip position="bottom" align="center" content={expandButtonLabel}>
<IconButton
className="rounded p-2"
className="rounded-sm p-2"
colorSchema="secondary"
variant="plain"
onClick={handleToggleView}
@@ -226,7 +226,7 @@ const AccessTreeContent = ({ permissions, subject, onClose }: AccessTreeProps) =
</Tooltip>
<Tooltip align="end" position="bottom" content={hideButtonLabel}>
<IconButton
className="rounded p-2"
className="rounded-sm p-2"
colorSchema="secondary"
variant="plain"
onClick={onClose}
@@ -55,13 +55,13 @@ const ErrorDisplay = ({
</p>
</div>
<div className="relative flex flex-1 flex-col overflow-hidden">
<pre className="thin-scrollbar w-full flex-1 overflow-y-auto whitespace-pre-wrap rounded bg-mineshaft-700 p-2 text-xs text-mineshaft-100">
<pre className="thin-scrollbar w-full flex-1 overflow-y-auto rounded-sm bg-mineshaft-700 p-2 text-xs whitespace-pre-wrap text-mineshaft-100">
{display}
</pre>
<IconButton
variant="plain"
colorSchema="secondary"
className="absolute right-4 top-2"
className="absolute top-2 right-4"
ariaLabel="Copy secret value"
onClick={copyToClipboard}
>
@@ -46,7 +46,7 @@ export const PermissionSimulation = ({
<Panel position="top-left">
<Button
size="xs"
className="mr-1 rounded"
className="mr-1 rounded-sm"
colorSchema="secondary"
onClick={handlePermissionSimulation}
rightIcon={
@@ -65,9 +65,9 @@ export const PermissionSimulation = ({
<Panel
onClick={handlePermissionSimulation}
position="top-left"
className={`group flex flex-col gap-2 pb-4 pr-4 ${expand ? "" : "cursor-pointer"}`}
className={`group flex flex-col gap-2 pr-4 pb-4 ${expand ? "" : "cursor-pointer"}`}
>
<div className="flex w-[20rem] flex-col gap-1.5 rounded border border-mineshaft-600 bg-mineshaft-800 p-2 font-inter text-gray-200">
<div className="flex w-[20rem] flex-col gap-1.5 rounded-sm border border-mineshaft-600 bg-mineshaft-800 p-2 font-inter text-gray-200">
<div>
<div className="flex w-full items-center justify-between">
<span className="text-sm">Permission Simulation</span>
@@ -83,7 +83,7 @@ export const PermissionSimulation = ({
</IconButton>
</div>
{expand && (
<p className="mb-2 mt-1 text-xs text-mineshaft-400">
<p className="mt-1 mb-2 text-xs text-mineshaft-400">
Evaluate conditional policies to see what permissions will be granted given a secret
name or tags
</p>
@@ -121,7 +121,7 @@ export const PermissionSimulation = ({
onValueChange={setEnvironment}
className="w-full border border-mineshaft-500 capitalize"
position="popper"
dropdownContainerClassName="max-w-[19rem]"
dropdownContainerClassName="max-w-76"
>
{environments.map(({ name, slug }) => {
return (
@@ -32,7 +32,7 @@ export const FolderNode = ({
<>
<Handle
type="target"
className="pointer-events-none !cursor-pointer opacity-0"
className="pointer-events-none cursor-pointer! opacity-0"
position={Position.Top}
/>
<div
@@ -42,7 +42,7 @@ export const FolderNode = ({
<FontAwesomeIcon className="mb-0.5 font-medium text-yellow" icon={faFolder} />
<span>{parentId ? `/${name}` : "/"}</span>
</div>
<div className="mt-1.5 flex w-full flex-wrap items-center justify-center gap-x-2 gap-y-1 rounded bg-mineshaft-600 px-2 py-1 text-xs">
<div className="mt-1.5 flex w-full flex-wrap items-center justify-center gap-x-2 gap-y-1 rounded-sm bg-mineshaft-600 px-2 py-1 text-xs">
{Object.entries(actions).map(([action, access]) => {
const { className, icon } = AccessMap[access];
@@ -70,7 +70,7 @@ export const FolderNode = ({
</div>
<Handle
type="source"
className="pointer-events-none !cursor-pointer opacity-0"
className="pointer-events-none cursor-pointer! opacity-0"
position={Position.Bottom}
/>
</>
@@ -61,7 +61,7 @@ export const AccessTreeSecretPathInput = ({
<div ref={wrapperRef} className="relative">
<div
className={twMerge(
"flex items-center overflow-hidden rounded transition-all duration-300 ease-in-out",
"flex items-center overflow-hidden rounded-sm transition-all duration-300 ease-in-out",
isFocused ? "bg-mineshaft-800 shadow-md" : "bg-mineshaft-700",
isExpanded ? "w-64" : "h-10 w-10"
)}
@@ -26,11 +26,11 @@ type ConditionDisplayProps = {
const ConditionDisplay = ({ _key: key, value, operator }: ConditionDisplayProps) => {
return (
<li>
<span className="font-medium capitalize text-mineshaft-100">{camelCaseToSpaces(key)}</span>{" "}
<span className="font-medium text-mineshaft-100 capitalize">{camelCaseToSpaces(key)}</span>{" "}
<span className="text-mineshaft-200">
{formatedConditionsOperatorNames[operator as PermissionConditionOperators]}
</span>{" "}
<span className="rounded bg-mineshaft-600 p-0.5 font-mono">
<span className="rounded-sm bg-mineshaft-600 p-0.5 font-mono">
{typeof value === "string" ? value : value.join(", ")}
</span>
.
@@ -45,7 +45,7 @@ export const FolderNodeTooltipContent = ({ action, access, actionRuleMap, subjec
case PermissionAccess.Full:
component = (
<>
<div className="flex items-center gap-1.5 capitalize text-green">
<div className="flex items-center gap-1.5 text-green capitalize">
<FontAwesomeIcon icon={faCheckCircle} size="xs" />
<span>Full {formatActionName(action)} Permissions</span>
</div>
@@ -62,7 +62,7 @@ export const FolderNodeTooltipContent = ({ action, access, actionRuleMap, subjec
case PermissionAccess.Partial:
component = (
<>
<div className="flex items-center gap-1.5 capitalize text-yellow">
<div className="flex items-center gap-1.5 text-yellow capitalize">
<FontAwesomeIcon icon={faCircleMinus} className="text-yellow" size="xs" />
<span>Conditional {formatActionName(action)} Permissions</span>
</div>
@@ -137,7 +137,7 @@ export const FolderNodeTooltipContent = ({ action, access, actionRuleMap, subjec
case PermissionAccess.None:
component = (
<>
<div className="flex items-center gap-1.5 capitalize text-red">
<div className="flex items-center gap-1.5 text-red capitalize">
<FontAwesomeIcon icon={faCircleXmark} size="xs" />
<span>No {formatActionName(action)} Permissions</span>
</div>
@@ -40,7 +40,7 @@ export const RoleNode = ({
<>
<Handle
type="target"
className="pointer-events-none !cursor-pointer opacity-0"
className="pointer-events-none cursor-pointer! opacity-0"
position={Position.Top}
/>
<div className="flex h-14 w-full flex-col items-center justify-center rounded-md border border-mineshaft bg-mineshaft-800 px-2 py-3 font-inter shadow-lg transition-opacity duration-500">
@@ -51,7 +51,7 @@ export const RoleNode = ({
</div>
<Handle
type="source"
className="pointer-events-none !cursor-pointer opacity-0"
className="pointer-events-none cursor-pointer! opacity-0"
position={Position.Bottom}
/>
</>
@@ -15,7 +15,7 @@ export const ShowMoreButtonNode = ({
<div className="flex h-full w-full items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-2">
<Handle
type="target"
className="pointer-events-none !cursor-pointer opacity-0"
className="pointer-events-none cursor-pointer! opacity-0"
position={Position.Top}
/>
@@ -69,16 +69,16 @@ export const PkiSyncImportStatusBadge = ({ pkiSync, className, mini }: Props) =>
label = "Failed to Import Certificates";
icon = faTriangleExclamation;
tooltipContent = (
<div className="flex flex-col gap-2 whitespace-normal py-1">
<div className="flex flex-col gap-2 py-1 whitespace-normal">
{failureMessage && (
<div>
<div className="mb-2 flex self-start text-red">
<FontAwesomeIcon icon={faXmark} className="ml-1 pr-1.5 pt-0.5 text-sm" />
<FontAwesomeIcon icon={faXmark} className="ml-1 pt-0.5 pr-1.5 text-sm" />
<div className="text-xs">
{mini ? "Failed to Import Certificates" : "Failure Reason"}
</div>
</div>
<div className="rounded bg-mineshaft-600 p-2 text-xs">{failureMessage}</div>
<div className="rounded-sm bg-mineshaft-600 p-2 text-xs">{failureMessage}</div>
</div>
)}
</div>
@@ -29,7 +29,7 @@ export const PkiSyncModalHeader = ({ destination, isConfigured }: Props) => {
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]" />
<FontAwesomeIcon icon={faBookOpen} className="mr-1 mb-[0.03rem] text-[12px]" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
@@ -69,16 +69,16 @@ export const PkiSyncRemoveStatusBadge = ({ pkiSync, className, mini }: Props) =>
label = "Failed to Remove Certificates";
icon = faTriangleExclamation;
tooltipContent = (
<div className="flex flex-col gap-2 whitespace-normal py-1">
<div className="flex flex-col gap-2 py-1 whitespace-normal">
{failureMessage && (
<div>
<div className="mb-2 flex self-start text-red">
<FontAwesomeIcon icon={faXmark} className="ml-1 pr-1.5 pt-0.5 text-sm" />
<FontAwesomeIcon icon={faXmark} className="ml-1 pt-0.5 pr-1.5 text-sm" />
<div className="text-xs">
{mini ? "Failed to Remove Certificates" : "Failure Reason"}
</div>
</div>
<div className="rounded bg-mineshaft-600 p-2 text-xs">{failureMessage}</div>
<div className="rounded-sm bg-mineshaft-600 p-2 text-xs">{failureMessage}</div>
</div>
)}
</div>
@@ -60,7 +60,7 @@ export const PkiSyncSelect = ({ onSelect }: Props) => {
placeholder="Search options..."
className="bg-mineshaft-800 placeholder:text-mineshaft-400"
/>
<div className="grid h-[29.5rem] grid-cols-4 content-start gap-2">
<div className="grid h-118 grid-cols-4 content-start gap-2">
{filteredOptions.slice(offset, perPage * page)?.map(({ destination, enterprise }) => {
const { image, name } = PKI_SYNC_MAP[destination];
return (
@@ -0,0 +1,50 @@
import { Controller, useFormContext } from "react-hook-form";
import { FormControl, Select, SelectItem } from "@app/components/v2";
import { AWS_REGIONS } from "@app/helpers/appConnections";
import { PkiSync } from "@app/hooks/api/pkiSyncs";
import { TPkiSyncForm } from "./schemas/pki-sync-schema";
import { PkiSyncConnectionField } from "./PkiSyncConnectionField";
export const AwsCertificateManagerPkiSyncFields = () => {
const { control, setValue } = useFormContext<
TPkiSyncForm & { destination: PkiSync.AwsCertificateManager }
>();
return (
<>
<PkiSyncConnectionField
onChange={() => {
setValue("destinationConfig.region", "");
}}
/>
<Controller
name="destinationConfig.region"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="AWS Region"
tooltipText="Select the AWS region where your Certificate Manager certificates should be stored."
>
<Select
value={field.value}
onValueChange={field.onChange}
className="w-full border border-mineshaft-500 capitalize"
position="popper"
placeholder="Select an AWS region"
>
{AWS_REGIONS.map(({ name, slug }) => (
<SelectItem value={slug} key={slug}>
{name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
</>
);
};
@@ -3,8 +3,8 @@ import { Controller, useFormContext } from "react-hook-form";
import { FormControl, Input } from "@app/components/v2";
import { PkiSync } from "@app/hooks/api/pkiSyncs";
import { TPkiSyncForm } from "./schemas/pki-sync-schema";
import { PkiSyncConnectionField } from "./PkiSyncConnectionField";
import { TPkiSyncForm } from "./schemas";
export const AzureKeyVaultPkiSyncFields = () => {
const { control, setValue } = useFormContext<
@@ -12,12 +12,12 @@ import { useProject } from "@app/context";
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
import { PkiSync, TPkiSync, useCreatePkiSync, usePkiSyncOption } from "@app/hooks/api/pkiSyncs";
import { PkiSyncFormSchema, TPkiSyncForm } from "./schemas/pki-sync-schema";
import { PkiSyncDestinationFields } from "./PkiSyncDestinationFields";
import { PkiSyncDetailsFields } from "./PkiSyncDetailsFields";
import { PkiSyncOptionsFields } from "./PkiSyncOptionsFields";
import { PkiSyncReviewFields } from "./PkiSyncReviewFields";
import { PkiSyncSourceFields } from "./PkiSyncSourceFields";
import { PkiSyncFormSchema, TPkiSyncForm } from "./schemas";
type Props = {
onComplete: (pkiSync: TPkiSync) => void;
@@ -124,7 +124,7 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel }: Props)
if (showConfirmation)
return (
<>
<div className="flex flex-col rounded-sm border border-l-[2px] border-mineshaft-600 border-l-primary bg-mineshaft-700/80 px-4 py-3">
<div className="flex flex-col rounded-xs border border-l-2 border-mineshaft-600 border-l-primary bg-mineshaft-700/80 px-4 py-3">
<div className="mb-1 flex items-center text-sm">
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1.5 text-primary" />
Certificate Sync Behavior
@@ -171,7 +171,7 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel }: Props)
setSelectedTabIndex((prev) => (isEnabled ? index : prev));
}}
className={({ selected }) =>
`w-30 -mb-[0.14rem] ${index > selectedTabIndex ? "opacity-30" : ""} px-4 py-2 text-sm font-medium outline-none disabled:opacity-60 ${
`-mb-[0.14rem] whitespace-nowrap ${index > selectedTabIndex ? "opacity-30" : ""} px-4 py-2 text-sm font-medium outline-hidden disabled:opacity-60 ${
selected
? "border-b-2 border-mineshaft-300 text-mineshaft-200"
: "text-bunker-300"
@@ -8,11 +8,11 @@ import { Button, ModalClose } from "@app/components/v2";
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
import { TPkiSync, useUpdatePkiSync } from "@app/hooks/api/pkiSyncs";
import { TUpdatePkiSyncForm, UpdatePkiSyncFormSchema } from "./schemas/pki-sync-schema";
import { PkiSyncDestinationFields } from "./PkiSyncDestinationFields";
import { PkiSyncDetailsFields } from "./PkiSyncDetailsFields";
import { PkiSyncOptionsFields } from "./PkiSyncOptionsFields";
import { PkiSyncSourceFields } from "./PkiSyncSourceFields";
import { TPkiSyncForm, UpdatePkiSyncFormSchema } from "./schemas";
type Props = {
onComplete: (pkiSync: TPkiSync) => void;
@@ -24,7 +24,7 @@ export const EditPkiSyncForm = ({ pkiSync, fields, onComplete }: Props) => {
const updatePkiSync = useUpdatePkiSync();
const { name: destinationName } = PKI_SYNC_MAP[pkiSync.destination];
const formMethods = useForm<TPkiSyncForm>({
const formMethods = useForm<TUpdatePkiSyncForm>({
resolver: zodResolver(UpdatePkiSyncFormSchema),
defaultValues: {
...pkiSync,
@@ -33,11 +33,11 @@ export const EditPkiSyncForm = ({ pkiSync, fields, onComplete }: Props) => {
id: pkiSync.connectionId,
name: pkiSync.appConnectionName
}
} as Partial<TPkiSyncForm>,
} as Partial<TUpdatePkiSyncForm>,
reValidateMode: "onChange"
});
const onSubmit = async ({ connection, ...formData }: TPkiSyncForm) => {
const onSubmit = async ({ connection, ...formData }: TUpdatePkiSyncForm) => {
try {
const updatedPkiSync = await updatePkiSync.mutateAsync({
syncId: pkiSync.id,
@@ -10,7 +10,7 @@ import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
import { PKI_SYNC_CONNECTION_MAP } from "@app/helpers/pkiSyncs";
import { useListAvailableAppConnections } from "@app/hooks/api/appConnections";
import { TPkiSyncForm } from "./schemas";
import { TPkiSyncForm } from "./schemas/pki-sync-schema";
type Props = {
onChange?: VoidFunction;
@@ -2,8 +2,9 @@ import { useFormContext } from "react-hook-form";
import { PkiSync } from "@app/hooks/api/pkiSyncs";
import { TPkiSyncForm } from "./schemas/pki-sync-schema";
import { AwsCertificateManagerPkiSyncFields } from "./AwsCertificateManagerPkiSyncFields";
import { AzureKeyVaultPkiSyncFields } from "./AzureKeyVaultPkiSyncFields";
import { TPkiSyncForm } from "./schemas";
export const PkiSyncDestinationFields = () => {
const { watch } = useFormContext<TPkiSyncForm>();
@@ -13,6 +14,8 @@ export const PkiSyncDestinationFields = () => {
switch (destination) {
case PkiSync.AzureKeyVault:
return <AzureKeyVaultPkiSyncFields />;
case PkiSync.AwsCertificateManager:
return <AwsCertificateManagerPkiSyncFields />;
default:
return (
<div className="flex items-center justify-center rounded-md border border-red-500 bg-red-100 p-4 text-red-700">
@@ -2,7 +2,7 @@ import { Controller, useFormContext } from "react-hook-form";
import { FormControl, Input, TextArea } from "@app/components/v2";
import { TPkiSyncForm } from "./schemas";
import { TPkiSyncForm } from "./schemas/pki-sync-schema";
export const PkiSyncDetailsFields = () => {
const { control } = useFormContext<TPkiSyncForm>();
@@ -38,7 +38,7 @@ export const PkiSyncDetailsFields = () => {
value={value}
onChange={onChange}
placeholder="Describe the purpose of this sync..."
className="!resize-none"
className="resize-none!"
rows={4}
/>
</FormControl>
@@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { FormControl, Input, Switch, Tooltip } from "@app/components/v2";
import { PkiSync, usePkiSyncOption } from "@app/hooks/api/pkiSyncs";
import { TPkiSyncForm } from "../schemas";
import { TPkiSyncForm } from "../schemas/pki-sync-schema";
type Props = {
destination?: PkiSync;
@@ -5,7 +5,7 @@ import { useProject } from "@app/context";
import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs";
import { useListWorkspacePkiSubscribers } from "@app/hooks/api";
import { TPkiSyncForm } from "./schemas";
import { TPkiSyncForm } from "./schemas/pki-sync-schema";
export const PkiSyncReviewFields = () => {
const { watch } = useFormContext<TPkiSyncForm>();
@@ -4,7 +4,7 @@ import { FilterableSelect, FormControl } from "@app/components/v2";
import { useProject } from "@app/context";
import { useListWorkspacePkiSubscribers } from "@app/hooks/api";
import { TPkiSyncForm } from "./schemas";
import { TPkiSyncForm } from "./schemas/pki-sync-schema";
export const PkiSyncSourceFields = () => {
const { control } = useFormContext<TPkiSyncForm>();
@@ -5,4 +5,4 @@ export { PkiSyncDetailsFields } from "./PkiSyncDetailsFields";
export { PkiSyncOptionsFields } from "./PkiSyncOptionsFields/PkiSyncOptionsFields";
export { PkiSyncReviewFields } from "./PkiSyncReviewFields";
export { PkiSyncSourceFields } from "./PkiSyncSourceFields";
export { PkiSyncFormSchema, type TPkiSyncForm } from "./schemas";
export { PkiSyncFormSchema, type TPkiSyncForm } from "./schemas/pki-sync-schema";
@@ -0,0 +1,65 @@
import { z } from "zod";
import { PkiSync } from "@app/hooks/api/pkiSyncs";
import { BasePkiSyncSchema } from "./base-pki-sync-schema";
const AwsCertificateManagerSyncOptionsSchema = z.object({
canImportCertificates: z.boolean().default(false),
canRemoveCertificates: z.boolean().default(false),
certificateNameSchema: z
.string()
.optional()
.refine(
(val) => {
// For AWS Certificate Manager, {{certificateId}} is always required if certificateNameSchema is provided
if (!val) return true;
if (!val.includes("{{certificateId}}")) {
return false;
}
const allowedOptionalPlaceholders = ["{{environment}}"];
const allowedPlaceholdersRegexPart = ["{{certificateId}}", ...allowedOptionalPlaceholders]
.map((p) => p.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"))
.join("|");
const allowedContentRegex = new RegExp(
`^([a-zA-Z0-9_\\-/]|${allowedPlaceholdersRegexPart})*$`
);
return allowedContentRegex.test(val);
},
{
message:
"Certificate name schema must include {{certificateId}} placeholder for AWS Certificate Manager."
}
)
});
export const AwsCertificateManagerPkiSyncDestinationSchema = BasePkiSyncSchema(
AwsCertificateManagerSyncOptionsSchema
).merge(
z.object({
destination: z.literal(PkiSync.AwsCertificateManager),
destinationConfig: z.object({
region: z.string().min(1, "AWS region is required")
})
})
);
export const UpdateAwsCertificateManagerPkiSyncDestinationSchema =
AwsCertificateManagerPkiSyncDestinationSchema.partial().merge(
z.object({
name: z
.string()
.trim()
.min(1, "Name is required")
.max(255, "Name must be less than 255 characters"),
destination: z.literal(PkiSync.AwsCertificateManager),
connection: z.object({
id: z.string().uuid("Invalid connection ID format"),
name: z.string().max(255, "Connection name must be less than 255 characters")
})
})
);
@@ -0,0 +1,33 @@
import { z } from "zod";
import { PkiSync } from "@app/hooks/api/pkiSyncs";
import { BasePkiSyncSchema } from "./base-pki-sync-schema";
export const AzureKeyVaultPkiSyncDestinationSchema = BasePkiSyncSchema().merge(
z.object({
destination: z.literal(PkiSync.AzureKeyVault),
destinationConfig: z.object({
vaultBaseUrl: z.string().min(1, "Vault base URL is required").url("Valid URL is required")
})
})
);
export const UpdateAzureKeyVaultPkiSyncDestinationSchema =
AzureKeyVaultPkiSyncDestinationSchema.partial().merge(
z.object({
name: z
.string()
.trim()
.min(1, "Name is required")
.max(255, "Name must be less than 255 characters"),
destination: z.literal(PkiSync.AzureKeyVault),
connection: z.object({
id: z.string().uuid("Invalid connection ID format"),
name: z
.string()
.min(1, "Connection name is required")
.max(255, "Connection name must be less than 255 characters")
})
})
);
@@ -1,25 +1,9 @@
import { z } from "zod";
import { AnyZodObject, z } from "zod";
import { PkiSync } from "@app/hooks/api/pkiSyncs";
export const PkiSyncFormSchema = z.object({
name: z
.string()
.trim()
.min(1, "Name is required")
.max(255, "Name must be less than 255 characters"),
description: z.string().optional(),
destination: z.nativeEnum(PkiSync),
isAutoSyncEnabled: z.boolean().default(true),
subscriberId: z.string().min(1, "PKI Subscriber is required"),
connection: z.object({
id: z.string().uuid("Invalid connection ID format"),
name: z.string().max(255, "Connection name must be less than 255 characters")
}),
destinationConfig: z.object({
vaultBaseUrl: z.string().url("Valid URL is required")
}),
syncOptions: z.object({
export const BasePkiSyncSchema = <T extends AnyZodObject | undefined = undefined>(
additionalSyncOptions?: T
) => {
const baseSyncOptionsSchema = z.object({
canImportCertificates: z.boolean().default(false),
canRemoveCertificates: z.boolean().default(false),
certificateNameSchema: z
@@ -53,22 +37,27 @@ export const PkiSyncFormSchema = z.object({
"Certificate name schema must include exactly one {{certificateId}} placeholder. It can also include {{environment}} placeholders. Only alphanumeric characters (a-z, A-Z, 0-9), dashes (-), underscores (_), and slashes (/) are allowed besides the placeholders."
}
)
})
});
});
export type TPkiSyncForm = z.infer<typeof PkiSyncFormSchema>;
const syncOptionsSchema = additionalSyncOptions
? baseSyncOptionsSchema.merge(additionalSyncOptions)
: (baseSyncOptionsSchema as T extends AnyZodObject
? z.ZodObject<z.objectUtil.MergeShapes<typeof baseSyncOptionsSchema.shape, T["shape"]>>
: typeof baseSyncOptionsSchema);
export const UpdatePkiSyncFormSchema = PkiSyncFormSchema.partial().merge(
z.object({
return z.object({
name: z
.string()
.trim()
.min(1, "Name is required")
.max(255, "Name must be less than 255 characters"),
destination: z.nativeEnum(PkiSync),
description: z.string().optional(),
isAutoSyncEnabled: z.boolean().default(true),
subscriberId: z.string().min(1, "PKI Subscriber is required"),
connection: z.object({
id: z.string().uuid("Invalid connection ID format"),
name: z.string().max(255, "Connection name must be less than 255 characters")
})
})
);
}),
syncOptions: syncOptionsSchema
});
};
@@ -0,0 +1,28 @@
import { z } from "zod";
import {
AwsCertificateManagerPkiSyncDestinationSchema,
UpdateAwsCertificateManagerPkiSyncDestinationSchema
} from "./aws-certificate-manager-pki-sync-destination-schema";
import {
AzureKeyVaultPkiSyncDestinationSchema,
UpdateAzureKeyVaultPkiSyncDestinationSchema
} from "./azure-key-vault-pki-sync-destination-schema";
const PkiSyncUnionSchema = z.discriminatedUnion("destination", [
AzureKeyVaultPkiSyncDestinationSchema,
AwsCertificateManagerPkiSyncDestinationSchema
]);
const UpdatePkiSyncUnionSchema = z.discriminatedUnion("destination", [
UpdateAzureKeyVaultPkiSyncDestinationSchema,
UpdateAwsCertificateManagerPkiSyncDestinationSchema
]);
export const PkiSyncFormSchema = PkiSyncUnionSchema;
export const UpdatePkiSyncFormSchema = UpdatePkiSyncUnionSchema;
export type TPkiSyncForm = z.infer<typeof PkiSyncFormSchema>;
export type TUpdatePkiSyncForm = z.infer<typeof UpdatePkiSyncFormSchema>;
@@ -85,7 +85,7 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="justify-betweens flex">
<h2 className="mb-8 flex-1 text-xl font-semibold text-mineshaft-100">Project Overview</h2>
<h2 className="mb-8 flex-1 text-xl font-medium text-mineshaft-100">Project Overview</h2>
<div className="space-x-2">
<Button
variant="outline_bg"
@@ -200,7 +200,7 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) =
placeholder="Project description"
{...field}
rows={3}
className="thin-scrollbar max-w-md !resize-none bg-mineshaft-800"
className="thin-scrollbar max-w-md resize-none! bg-mineshaft-800"
isDisabled={!isAllowed}
/>
</FormControl>
@@ -80,11 +80,11 @@ const PROJECT_TYPE_MENU_ITEMS = [
{
label: "Secret Scanning",
value: ProjectType.SecretScanning
},
{
label: "PAM",
value: ProjectType.PAM
}
// {
// label: "PAM",
// value: ProjectType.PAM
// }
];
const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
@@ -202,7 +202,7 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
<div
key={el.value}
className={twMerge(
"flex cursor-pointer flex-col items-center gap-2 rounded border border-mineshaft-600 px-2 py-4 opacity-75 transition-all hover:border-primary-400 hover:bg-mineshaft-600",
"flex cursor-pointer flex-col items-center gap-2 rounded-sm border border-mineshaft-600 px-2 py-4 opacity-75 transition-all hover:border-primary-400 hover:bg-mineshaft-600",
field.value === el.value && "border-primary-400 bg-mineshaft-600 opacity-100"
)}
onClick={() => field.onChange(el.value)}
@@ -238,7 +238,7 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
placeholder="Project description"
{...field}
rows={3}
className="thin-scrollbar w-full !resize-none bg-mineshaft-900"
className="thin-scrollbar w-full resize-none! bg-mineshaft-900"
/>
</FormControl>
)}
@@ -333,7 +333,7 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
</AccordionContent>
</AccordionItem>
</Accordion>
<div className="absolute bottom-0 right-0 mb-6 mr-6 flex items-start justify-end">
<div className="absolute right-0 bottom-0 mr-6 mb-6 flex items-start justify-end">
<ModalClose>
<Button colorSchema="secondary" variant="plain" className="py-2">
Cancel
@@ -124,7 +124,7 @@ export const CreateSecretRotationV2Modal = ({ onOpenChange, isOpen, ...props }:
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]" />
<FontAwesomeIcon icon={faBookOpen} className="mr-1 mb-[0.03rem] text-[12px]" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
@@ -29,7 +29,7 @@ export const SecretRotationV2ModalHeader = ({ type, isConfigured }: Props) => {
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]" />
<FontAwesomeIcon icon={faBookOpen} className="mr-1 mb-[0.03rem] text-[12px]" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
@@ -31,13 +31,15 @@ export const SecretRotationV2StatusBadge = ({ secretRotation, className }: Props
position="left"
className="max-w-sm select-text"
content={
<div className="flex flex-col gap-2 whitespace-normal py-1">
<div className="flex flex-col gap-2 py-1 whitespace-normal">
<div>
<div className="mb-2 flex self-start text-red">
<FontAwesomeIcon icon={faXmark} className="ml-1 pr-1.5 pt-0.5 text-sm" />
<FontAwesomeIcon icon={faXmark} className="ml-1 pt-0.5 pr-1.5 text-sm" />
<div className="text-xs">Failure Reason</div>
</div>
<div className="break-words rounded bg-mineshaft-600 p-2 text-xs">{errorMessage}</div>
<div className="rounded-sm bg-mineshaft-600 p-2 text-xs break-words">
{errorMessage}
</div>
</div>
{nextRotationAt && (
<span className="text-xs text-mineshaft-300">
@@ -65,7 +67,7 @@ export const SecretRotationV2StatusBadge = ({ secretRotation, className }: Props
return (
<Badge
className={twMerge(
"flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300",
"flex h-5 w-min items-center gap-1.5 bg-mineshaft-400/50 whitespace-nowrap text-bunker-300",
className
)}
>
@@ -23,7 +23,7 @@ export const ViewRotationGeneratedCredentialsDisplay = ({
<p className="text-sm text-mineshaft-300">
The active credential set currently mapped to the rotation secrets.
</p>
<div className="flex flex-col gap-x-8 gap-y-2 rounded border border-mineshaft-600 bg-mineshaft-700 p-2">
<div className="flex flex-col gap-x-8 gap-y-2 rounded-sm border border-mineshaft-600 bg-mineshaft-700 p-2">
{activeCredentials}
</div>
</div>
@@ -38,7 +38,7 @@ export const ViewRotationGeneratedCredentialsDisplay = ({
<p className="text-sm text-mineshaft-300">
The retired credential set that will be revoked during the next rotation cycle.
</p>
<div className="flex flex-col gap-x-8 gap-y-2 rounded border border-mineshaft-600 bg-mineshaft-700 p-2">
<div className="flex flex-col gap-x-8 gap-y-2 rounded-sm border border-mineshaft-600 bg-mineshaft-700 p-2">
{inactiveCredentials}
</div>
</div>
@@ -90,7 +90,7 @@ export const SecretRotationV2ConfigurationFields = ({ isUpdate, environments }:
});
}
}}
className="bg-mineshaft-700 text-white [color-scheme:dark]"
className="bg-mineshaft-700 text-white scheme-dark"
/>
</FormControl>
);
@@ -126,7 +126,7 @@ export const SecretRotationV2ConfigurationFields = ({ isUpdate, environments }:
}}
/>
{!IS_ROTATION_DUAL_CREDENTIALS[type] && isAutoRotationEnabled && (
<div className="rounded border border-yellow bg-yellow/10 p-2 px-3 text-xs text-yellow">
<div className="rounded-sm border border-yellow bg-yellow/10 p-2 px-3 text-xs text-yellow">
<FontAwesomeIcon icon={faWarning} className="mr-1" /> Due to{" "}
{SECRET_ROTATION_MAP[type].name} Rotations rotating a single credential set, auto-rotation
may result in service interruptions. If you need to ensure service continuity, we
@@ -38,7 +38,7 @@ export const SecretRotationV2DetailsFields = () => {
value={value ?? ""}
onChange={onChange}
placeholder="Describe the purpose of this rotation..."
className="!resize-none"
className="resize-none!"
rows={4}
/>
</FormControl>
@@ -192,7 +192,7 @@ export const SecretRotationV2Form = ({
setSelectedTabIndex((prev) => (isEnabled ? index : prev));
}}
className={({ selected }) =>
`w-30 -mb-[0.14rem] ${index > selectedTabIndex ? "opacity-30" : ""} px-4 py-2 text-sm font-medium outline-none disabled:opacity-60 ${
`-mb-[0.14rem] whitespace-nowrap ${index > selectedTabIndex ? "opacity-30" : ""} px-4 py-2 text-sm font-medium outline-hidden disabled:opacity-60 ${
selected
? "border-b-2 border-mineshaft-300 text-mineshaft-200"
: "text-bunker-300"
@@ -38,7 +38,7 @@ export const Auth0ClientSecretRotationParametersFields = () => {
content={
<>
Ensure that your connection has the{" "}
<span className="font-semibold">read_clients</span> permission and the application
<span className="font-medium">read_clients</span> permission and the application
exists in the connection&#39;s audience.
</>
}
@@ -38,7 +38,7 @@ export const AzureClientSecretRotationParametersFields = () => {
content={
<>
Ensure that your connection has the{" "}
<span className="font-semibold">
<span className="font-medium">
Application.ReadWrite.All, Directory.ReadWrite.All,
Application.ReadWrite.OwnedBy, user_impersonation and User.Read
</span>{" "}
@@ -27,7 +27,7 @@ export const LdapPasswordRotationParametersFields = () => {
tooltipText={
<>
<span>Determines how the rotation will be performed:</span>
<ul className="ml-4 mt-2 flex list-disc flex-col gap-2">
<ul className="mt-2 ml-4 flex list-disc flex-col gap-2">
<li>
<span className="font-medium">Connection Principal</span> - The Connection
principal will rotate the target principal&#39;s password.
@@ -130,7 +130,7 @@ export const LdapPasswordRotationParametersFields = () => {
<div className="w-full border-b border-mineshaft-600">
<span className="text-sm text-mineshaft-300">Password Requirements</span>
</div>
<div className="grid grid-cols-2 gap-x-3 gap-y-1 rounded border border-mineshaft-600 bg-mineshaft-700 px-3 pt-3">
<div className="grid grid-cols-2 gap-x-3 gap-y-1 rounded-sm border border-mineshaft-600 bg-mineshaft-700 px-3 pt-3">
<Controller
control={control}
name="parameters.passwordRequirements.length"
@@ -22,7 +22,7 @@ export const RedisCredentialsRotationParametersFields = () => {
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
tooltipClassName="max-w-[40rem] w-full"
tooltipClassName="max-w-160 w-full"
tooltipText={
<div className="flex flex-col gap-4">
<p>
@@ -31,7 +31,7 @@ export const RedisCredentialsRotationParametersFields = () => {
</p>
<p>
The default value is{" "}
<code className="rounded bg-mineshaft-700 px-1 py-0.5 font-mono font-medium text-bunker-300">
<code className="rounded-sm bg-mineshaft-700 px-1 py-0.5 font-mono font-medium text-bunker-300">
~* +@all
</code>
. You can modify it to suit your needs.
@@ -64,7 +64,7 @@ export const RedisCredentialsRotationParametersFields = () => {
<div className="w-full border-b border-mineshaft-600">
<span className="text-sm text-mineshaft-300">Password Requirements</span>
</div>
<div className="grid grid-cols-2 gap-x-3 gap-y-1 rounded border border-mineshaft-600 bg-mineshaft-700 px-3 pt-3">
<div className="grid grid-cols-2 gap-x-3 gap-y-1 rounded-sm border border-mineshaft-600 bg-mineshaft-700 px-3 pt-3">
<Controller
control={control}
name="parameters.passwordRequirements.length"
@@ -74,7 +74,7 @@ export const SqlCredentialsRotationParametersFields = () => {
to suit your needs.
</p>
<p className="mb-3 text-sm">
<pre className="max-h-[10rem] overflow-y-auto whitespace-pre-wrap rounded border border-mineshaft-700 bg-mineshaft-800 p-2 text-mineshaft-300">
<pre className="max-h-40 overflow-y-auto rounded-sm border border-mineshaft-700 bg-mineshaft-800 p-2 whitespace-pre-wrap text-mineshaft-300">
{rotationOption!.template.createUserStatement}
</pre>
</p>
@@ -32,13 +32,13 @@ export const SecretsMappingTable = ({ items }: Props) => {
<tr key={name}>
<td className="whitespace-nowrap">
<div className="mb-4 flex h-full items-start justify-center">
<Badge className="pointer-events-none flex h-[36px] w-full items-center justify-center gap-1.5 whitespace-nowrap border border-mineshaft-600 bg-mineshaft-600 text-bunker-200">
<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">
<FontAwesomeIcon icon={faKey} />
<span>{name}</span>
</Badge>
</div>
</td>
<td className="whitespace-nowrap pl-5 pr-5">
<td className="pr-5 pl-5 whitespace-nowrap">
<div className="mb-4 flex items-center justify-center">
<FontAwesomeIcon className="text-mineshaft-400" icon={faArrowRight} />
</div>
@@ -117,7 +117,7 @@ export const CreateSecretScanningDataSourceModal = ({ onOpenChange, isOpen, ...p
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]" />
<FontAwesomeIcon icon={faBookOpen} className="mr-1 mb-[0.03rem] text-[12px]" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
@@ -29,7 +29,7 @@ export const SecretScanningDataSourceModalHeader = ({ type, isConfigured }: Prop
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]" />
<FontAwesomeIcon icon={faBookOpen} className="mr-1 mb-[0.03rem] text-[12px]" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
@@ -34,13 +34,15 @@ export const SecretScanningScanStatusBadge = ({
position="left"
className="max-w-sm select-text"
content={
<div className="flex flex-col gap-2 whitespace-normal py-1">
<div className="flex flex-col gap-2 py-1 whitespace-normal">
<div>
<div className="mb-2 flex self-start text-red">
<FontAwesomeIcon icon={faXmark} className="ml-1 pr-1.5 pt-0.5 text-sm" />
<FontAwesomeIcon icon={faXmark} className="ml-1 pt-0.5 pr-1.5 text-sm" />
<div className="text-xs">Failure Reason</div>
</div>
<div className="break-words rounded bg-mineshaft-600 p-2 text-xs">{errorMessage}</div>
<div className="rounded-sm bg-mineshaft-600 p-2 text-xs break-words">
{errorMessage}
</div>
{scannedAt && (
<div className="mt-1 text-xs text-mineshaft-400">
Attempted {formatDistance(new Date(scannedAt), new Date(), { addSuffix: true })}
@@ -38,7 +38,7 @@ export const SecretScanningDataSourceDetailsFields = () => {
value={value ?? ""}
onChange={onChange}
placeholder="Provide a description for this data source..."
className="!resize-none"
className="resize-none!"
rows={4}
/>
</FormControl>
@@ -145,7 +145,7 @@ export const SecretScanningDataSourceForm = ({
setSelectedTabIndex((prev) => (isEnabled ? index : prev));
}}
className={({ selected }) =>
`w-30 -mb-[0.14rem] ${index > selectedTabIndex ? "opacity-30" : ""} px-4 py-2 text-sm font-medium outline-none disabled:opacity-60 ${
`-mb-[0.14rem] whitespace-nowrap ${index > selectedTabIndex ? "opacity-30" : ""} px-4 py-2 text-sm font-medium outline-hidden disabled:opacity-60 ${
selected
? "border-b-2 border-mineshaft-300 text-mineshaft-200"
: "text-bunker-300"
@@ -69,16 +69,16 @@ export const SecretSyncImportStatusBadge = ({ secretSync, className, mini }: Pro
label = "Failed to Import Secrets";
icon = faTriangleExclamation;
tooltipContent = (
<div className="flex flex-col gap-2 whitespace-normal py-1">
<div className="flex flex-col gap-2 py-1 whitespace-normal">
{failureMessage && (
<div>
<div className="mb-2 flex self-start text-red">
<FontAwesomeIcon icon={faXmark} className="ml-1 pr-1.5 pt-0.5 text-sm" />
<FontAwesomeIcon icon={faXmark} className="ml-1 pt-0.5 pr-1.5 text-sm" />
<div className="text-xs">
{mini ? "Failed to Import Secrets" : "Failure Reason"}
</div>
</div>
<div className="rounded bg-mineshaft-600 p-2 text-xs">{failureMessage}</div>
<div className="rounded-sm bg-mineshaft-600 p-2 text-xs">{failureMessage}</div>
</div>
)}
</div>
@@ -29,7 +29,7 @@ export const SecretSyncModalHeader = ({ destination, isConfigured }: Props) => {
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]" />
<FontAwesomeIcon icon={faBookOpen} className="mr-1 mb-[0.03rem] text-[12px]" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
@@ -69,16 +69,16 @@ export const SecretSyncRemoveStatusBadge = ({ secretSync, className, mini }: Pro
label = "Failed to Remove Secrets";
icon = faTriangleExclamation;
tooltipContent = (
<div className="flex flex-col gap-2 whitespace-normal py-1">
<div className="flex flex-col gap-2 py-1 whitespace-normal">
{failureMessage && (
<div>
<div className="mb-2 flex self-start text-red">
<FontAwesomeIcon icon={faXmark} className="ml-1 pr-1.5 pt-0.5 text-sm" />
<FontAwesomeIcon icon={faXmark} className="ml-1 pt-0.5 pr-1.5 text-sm" />
<div className="text-xs">
{mini ? "Failed to Remove Secrets" : "Failure Reason"}
</div>
</div>
<div className="rounded bg-mineshaft-600 p-2 text-xs">{failureMessage}</div>
<div className="rounded-sm bg-mineshaft-600 p-2 text-xs">{failureMessage}</div>
</div>
)}
</div>
@@ -58,7 +58,7 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
placeholder="Search options..."
className="bg-mineshaft-800 placeholder:text-mineshaft-400"
/>
<div className="grid h-[29.5rem] grid-cols-4 content-start gap-2">
<div className="grid h-118 grid-cols-4 content-start gap-2">
{filteredOptions.slice(offset, perPage * page)?.map(({ destination, enterprise }) => {
const { image, name } = SECRET_SYNC_MAP[destination];
return (
@@ -138,7 +138,7 @@ export const CreateSecretSyncForm = ({
if (showConfirmation)
return (
<>
<div className="flex flex-col rounded-sm border border-l-[2px] border-mineshaft-600 border-l-primary bg-mineshaft-700/80 px-4 py-3">
<div className="flex flex-col rounded-xs border border-l-2 border-mineshaft-600 border-l-primary bg-mineshaft-700/80 px-4 py-3">
<div className="mb-1 flex items-center text-sm">
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1.5 text-primary" />
Secret Sync Behavior
@@ -185,7 +185,7 @@ export const CreateSecretSyncForm = ({
setSelectedTabIndex((prev) => (isEnabled ? index : prev));
}}
className={({ selected }) =>
`w-30 -mb-[0.14rem] ${index > selectedTabIndex ? "opacity-30" : ""} px-4 py-2 text-sm font-medium outline-none disabled:opacity-60 ${
`-mb-[0.14rem] whitespace-nowrap ${index > selectedTabIndex ? "opacity-30" : ""} px-4 py-2 text-sm font-medium outline-hidden disabled:opacity-60 ${
selected
? "border-b-2 border-mineshaft-300 text-mineshaft-200"
: "text-bunker-300"
@@ -0,0 +1,57 @@
import { Button, Modal, ModalClose, ModalContent } from "@app/components/v2";
type Props = {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
onConfirm: () => void;
isLoading?: boolean;
duplicateProjectId?: string;
};
export const DuplicateDestinationConfirmationModal = ({
isOpen,
onOpenChange,
onConfirm,
isLoading,
duplicateProjectId
}: Props) => {
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent className="max-w-lg" title="Duplicate Destination Configuration">
<div className="mb-4 text-sm">
<p>
Another secret sync in your organization is already configured with the same
destination. Proceeding may cause conflicts or overwrite existing data.
</p>
{duplicateProjectId && (
<p className="mt-2 text-xs text-mineshaft-400">
Duplicate found in project ID:{" "}
<code className="rounded-sm bg-mineshaft-600 px-1 py-0.5 text-mineshaft-200">
{duplicateProjectId}
</code>
</p>
)}
<p className="mt-2">Are you sure you want to continue?</p>
</div>
<div className="flex items-center gap-4 pt-4">
<ModalClose asChild>
<Button
onClick={onConfirm}
colorSchema="danger"
isLoading={isLoading}
isDisabled={isLoading}
>
Continue
</Button>
</ModalClose>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain" isDisabled={isLoading}>
Cancel
</Button>
</ModalClose>
</div>
</ModalContent>
</Modal>
);
};
@@ -1,4 +1,4 @@
import { ReactNode } from "react";
import { ReactNode, useCallback, useEffect, useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -6,9 +6,14 @@ import { createNotification } from "@app/components/notifications";
import { SecretSyncEditFields } from "@app/components/secret-syncs/types";
import { Button, ModalClose } from "@app/components/v2";
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { TSecretSync, useUpdateSecretSync } from "@app/hooks/api/secretSyncs";
import {
TSecretSync,
useCheckDuplicateDestination,
useUpdateSecretSync
} from "@app/hooks/api/secretSyncs";
import { SecretSyncOptionsFields } from "./SecretSyncOptionsFields/SecretSyncOptionsFields";
import { DuplicateDestinationConfirmationModal } from "./DuplicateDestinationConfirmationModal";
import { TSecretSyncForm, UpdateSecretSyncFormSchema } from "./schemas";
import { SecretSyncDestinationFields } from "./SecretSyncDestinationFields";
import { SecretSyncDetailsFields } from "./SecretSyncDetailsFields";
@@ -23,6 +28,8 @@ type Props = {
export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) => {
const updateSecretSync = useUpdateSecretSync();
const { name: destinationName } = SECRET_SYNC_MAP[secretSync.destination];
const [showDuplicateConfirmation, setShowDuplicateConfirmation] = useState(false);
const [pendingFormData, setPendingFormData] = useState<TSecretSyncForm | null>(null);
const formMethods = useForm<TSecretSyncForm>({
resolver: zodResolver(UpdateSecretSyncFormSchema),
@@ -35,29 +42,114 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) =>
reValidateMode: "onChange"
});
const onSubmit = async ({ environment, connection, ...formData }: TSecretSyncForm) => {
try {
const updatedSecretSync = await updateSecretSync.mutateAsync({
syncId: secretSync.id,
...formData,
environment: environment?.slug,
connectionId: connection.id,
projectId: secretSync.projectId
const [destinationConfigToCheck, setDestinationConfigToCheck] = useState<unknown>(null);
const [checkDuplicateEnabled, setCheckDuplicateEnabled] = useState(false);
const [storedDuplicateProjectId, setStoredDuplicateProjectId] = useState<string | undefined>();
const { data: duplicateData, isLoading: isCheckingDuplicate } = useCheckDuplicateDestination(
secretSync.destination,
destinationConfigToCheck,
secretSync.projectId,
secretSync.id,
{ enabled: checkDuplicateEnabled && Boolean(destinationConfigToCheck) }
);
const performUpdate = useCallback(
async (formData: TSecretSyncForm) => {
try {
const { environment, connection, ...updateData } = formData;
const updatedSecretSync = await updateSecretSync.mutateAsync({
syncId: secretSync.id,
...updateData,
environment: environment?.slug,
connectionId: connection.id,
projectId: secretSync.projectId
});
createNotification({
text: `Successfully updated ${destinationName} Sync`,
type: "success"
});
onComplete(updatedSecretSync);
} catch (err: any) {
console.error(err);
createNotification({
title: `Failed to update ${destinationName} Sync`,
text: err.message,
type: "error"
});
}
},
[updateSecretSync, secretSync.id, secretSync.projectId, destinationName, onComplete]
);
useEffect(() => {
if (checkDuplicateEnabled && !isCheckingDuplicate && destinationConfigToCheck) {
if (duplicateData?.hasDuplicate) {
setStoredDuplicateProjectId(duplicateData.duplicateProjectId);
setShowDuplicateConfirmation(true);
} else if (pendingFormData) {
performUpdate(pendingFormData);
setPendingFormData(null);
}
setCheckDuplicateEnabled(false);
setDestinationConfigToCheck(null);
}
}, [
checkDuplicateEnabled,
isCheckingDuplicate,
duplicateData?.hasDuplicate,
duplicateData?.duplicateProjectId,
destinationConfigToCheck,
pendingFormData,
performUpdate
]);
const normalizeConfig = (config: unknown): unknown => {
if (config === null || config === undefined || typeof config !== "object") {
return config;
}
if (Array.isArray(config)) {
return config.map(normalizeConfig);
}
const normalized: Record<string, unknown> = {};
Object.keys(config as Record<string, unknown>)
.sort()
.forEach((key) => {
normalized[key] = normalizeConfig((config as Record<string, unknown>)[key]);
});
createNotification({
text: `Successfully updated ${destinationName} Sync`,
type: "success"
});
onComplete(updatedSecretSync);
} catch (err: any) {
console.error(err);
createNotification({
title: `Failed to update ${destinationName} Sync`,
text: err.message,
type: "error"
});
return normalized;
};
const hasDestinationConfigChanged = (formData: TSecretSyncForm) => {
const originalConfig = normalizeConfig(secretSync.destinationConfig);
const currentConfig = normalizeConfig(formData.destinationConfig);
return JSON.stringify(originalConfig) !== JSON.stringify(currentConfig);
};
const onSubmit = async (formData: TSecretSyncForm) => {
if (fields === SecretSyncEditFields.Destination && hasDestinationConfigChanged(formData)) {
setDestinationConfigToCheck(formData.destinationConfig);
setPendingFormData(formData);
setCheckDuplicateEnabled(true);
return;
}
await performUpdate(formData);
};
const handleConfirmDuplicate = async () => {
if (pendingFormData) {
await performUpdate(pendingFormData);
setPendingFormData(null);
}
setShowDuplicateConfirmation(false);
setCheckDuplicateEnabled(false);
setDestinationConfigToCheck(null);
};
let Component: ReactNode;
@@ -83,24 +175,41 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) =>
formState: { isSubmitting, isDirty }
} = formMethods;
const isLoading = isSubmitting || isCheckingDuplicate;
return (
<form onSubmit={handleSubmit(onSubmit)}>
<FormProvider {...formMethods}>{Component}</FormProvider>
<div className="flex w-full justify-between gap-4 pt-4">
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
<>
<form onSubmit={handleSubmit(onSubmit)}>
<FormProvider {...formMethods}>{Component}</FormProvider>
<div className="flex w-full justify-between gap-4 pt-4">
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
<Button
isLoading={isLoading}
isDisabled={!isDirty || isLoading}
type="submit"
colorSchema="secondary"
>
{isCheckingDuplicate ? "Checking..." : "Update Sync"}
</Button>
</ModalClose>
<Button
isLoading={isSubmitting}
isDisabled={!isDirty || isSubmitting}
type="submit"
colorSchema="secondary"
>
Update Sync
</Button>
</div>
</form>
</div>
</form>
<DuplicateDestinationConfirmationModal
isOpen={showDuplicateConfirmation}
onOpenChange={(open) => {
setShowDuplicateConfirmation(open);
if (!open) {
setStoredDuplicateProjectId(undefined);
}
}}
onConfirm={handleConfirmDuplicate}
isLoading={updateSecretSync.isPending}
duplicateProjectId={storedDuplicateProjectId}
/>
</>
);
};
@@ -38,15 +38,15 @@ export const AwsParameterStoreSyncFields = () => {
<>
The path is required and will be prepended to the key schema. For example, if you
have a path of{" "}
<code className="rounded bg-mineshaft-600 px-0.5 py-px text-sm text-mineshaft-300">
<code className="rounded-sm bg-mineshaft-600 px-0.5 py-px text-sm text-mineshaft-300">
/demo/path/
</code>{" "}
and a key schema of{" "}
<code className="rounded bg-mineshaft-600 px-0.5 py-px text-sm text-mineshaft-300">
<code className="rounded-sm bg-mineshaft-600 px-0.5 py-px text-sm text-mineshaft-300">
INFISICAL_{"{{secretKey}}"}
</code>
, then the result will be{" "}
<code className="rounded bg-mineshaft-600 px-0.5 py-px text-sm text-mineshaft-300">
<code className="rounded-sm bg-mineshaft-600 px-0.5 py-px text-sm text-mineshaft-300">
/demo/path/INFISICAL_{"{{secretKey}}"}
</code>
</>
@@ -10,7 +10,7 @@ const Option = ({ isSelected, children, ...props }: OptionProps<(typeof AWS_REGI
<components.Option isSelected={isSelected} {...props}>
<div className="flex flex-row items-center justify-between">
<p className="truncate">{children}</p>
<Badge variant="success" className="ml-1 mr-auto cursor-pointer">
<Badge variant="success" className="mr-auto ml-1 cursor-pointer">
{props.data.slug}
</Badge>
{isSelected && (
@@ -38,7 +38,7 @@ export const SecretSyncDetailsFields = () => {
value={value}
onChange={onChange}
placeholder="Describe the purpose of this sync..."
className="!resize-none"
className="resize-none!"
rows={4}
/>
</FormControl>
@@ -33,7 +33,7 @@ const AwsTagsSection = () => {
});
return (
<div className="mb-4 mt-2 flex flex-col pl-2">
<div className="mt-2 mb-4 flex flex-col pl-2">
<div className="grid max-h-[20vh] grid-cols-12 items-end gap-2 overflow-y-auto">
{tagFields.fields.map(({ id: tagFieldId }, i) => (
<Fragment key={tagFieldId}>
@@ -143,19 +143,19 @@ export const AwsParameterStoreSyncOptionsFields = () => {
<p>
To configure a KMS key, ensure the following permissions are present on the
selected IAM role:{" "}
<span className="rounded bg-mineshaft-600 text-mineshaft-300">
<span className="rounded-sm bg-mineshaft-600 text-mineshaft-300">
&#34;kms:ListAliases&#34;
</span>
,{" "}
<span className="rounded bg-mineshaft-600 text-mineshaft-300">
<span className="rounded-sm bg-mineshaft-600 text-mineshaft-300">
&#34;kms:DescribeKey&#34;
</span>
,{" "}
<span className="rounded bg-mineshaft-600 text-mineshaft-300">
<span className="rounded-sm bg-mineshaft-600 text-mineshaft-300">
&#34;kms:Encrypt&#34;
</span>
,{" "}
<span className="rounded bg-mineshaft-600 text-mineshaft-300">
<span className="rounded-sm bg-mineshaft-600 text-mineshaft-300">
&#34;kms:Decrypt&#34;
</span>
.
@@ -34,7 +34,7 @@ const AwsTagsSection = () => {
});
return (
<div className="mb-4 mt-2 flex flex-col pl-2">
<div className="mt-2 mb-4 flex flex-col pl-2">
<div className="grid max-h-[20vh] grid-cols-12 items-end gap-2 overflow-y-auto">
{tagFields.fields.map(({ id: tagFieldId }, i) => (
<Fragment key={tagFieldId}>
@@ -145,19 +145,19 @@ export const AwsSecretsManagerSyncOptionsFields = () => {
<p>
To configure a KMS key, ensure the following permissions are present on the
selected IAM role:{" "}
<span className="rounded bg-mineshaft-600 text-mineshaft-300">
<span className="rounded-sm bg-mineshaft-600 text-mineshaft-300">
&#34;kms:ListAliases&#34;
</span>
,{" "}
<span className="rounded bg-mineshaft-600 text-mineshaft-300">
<span className="rounded-sm bg-mineshaft-600 text-mineshaft-300">
&#34;kms:DescribeKey&#34;
</span>
,{" "}
<span className="rounded bg-mineshaft-600 text-mineshaft-300">
<span className="rounded-sm bg-mineshaft-600 text-mineshaft-300">
&#34;kms:Encrypt&#34;
</span>
,{" "}
<span className="rounded bg-mineshaft-600 text-mineshaft-300">
<span className="rounded-sm bg-mineshaft-600 text-mineshaft-300">
&#34;kms:Decrypt&#34;
</span>
.
@@ -224,7 +224,7 @@ export const AwsSecretsManagerSyncOptionsFields = () => {
isChecked={value}
onCheckedChange={onChange}
>
<p className="w-[14rem]">
<p className="w-56">
Sync Secret Metadata as Tags{" "}
<Tooltip
className="max-w-md"
@@ -224,14 +224,14 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
onCheckedChange={onChange}
isChecked={value}
>
<p className="w-[11rem]">
<p className="w-44">
Disable Secret Deletion{" "}
<Tooltip
className="max-w-md"
content={
<>
<p>
When enabled, Infisical will <span className="font-semibold">not</span>{" "}
When enabled, Infisical will <span className="font-medium">not</span>{" "}
remove secrets from the destination during a sync.
</p>
<p className="mt-4">
@@ -26,7 +26,7 @@ export const AwsParameterStoreSyncOptionsReviewFields = () => {
content={
<Table>
<THead>
<Th className="whitespace-nowrap p-2">Key</Th>
<Th className="p-2 whitespace-nowrap">Key</Th>
<Th className="p-2">Value</Th>
</THead>
<TBody>
@@ -41,7 +41,7 @@ export const AwsParameterStoreSyncOptionsReviewFields = () => {
}
>
<div className="w-min">
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
<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" : ""}
@@ -58,7 +58,7 @@ export const AwsSecretsManagerSyncOptionsReviewFields = () => {
content={
<Table>
<THead>
<Th className="whitespace-nowrap p-2">Key</Th>
<Th className="p-2 whitespace-nowrap">Key</Th>
<Th className="p-2">Value</Th>
</THead>
<TBody>
@@ -73,7 +73,7 @@ export const AwsSecretsManagerSyncOptionsReviewFields = () => {
}
>
<div className="w-min">
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
<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" : ""}
@@ -1,11 +1,14 @@
import { ReactNode } from "react";
import { useFormContext } from "react-hook-form";
import { faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
import { Badge } from "@app/components/v2";
import { useProject } from "@app/context";
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { SecretSync, useDuplicateDestinationCheck } from "@app/hooks/api/secretSyncs";
import {
AwsParameterStoreDestinationReviewFields,
@@ -46,6 +49,7 @@ import { ZabbixSyncReviewFields } from "./ZabbixSyncReviewFields";
export const SecretSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm>();
const { currentProject } = useProject();
let DestinationFieldsComponent: ReactNode;
let AdditionalSyncOptionsFieldsComponent: ReactNode;
@@ -63,6 +67,13 @@ export const SecretSyncReviewFields = () => {
const destinationName = SECRET_SYNC_MAP[destination].name;
const { hasDuplicate, duplicateProjectId, isChecking } = useDuplicateDestinationCheck({
destination,
projectId: currentProject?.id || "",
enabled: true,
destinationConfig: watch("destinationConfig")
});
switch (destination) {
case SecretSync.AWSParameterStore:
DestinationFieldsComponent = <AwsParameterStoreDestinationReviewFields />;
@@ -173,9 +184,31 @@ export const SecretSyncReviewFields = () => {
</div>
</div>
<div className="flex flex-col gap-3">
<div className="w-full border-b border-mineshaft-600">
<div className="flex w-full items-center gap-2 border-b border-mineshaft-600">
<span className="text-sm text-mineshaft-300">Destination</span>
{isChecking && <span className="text-xs text-mineshaft-400">Checking...</span>}
</div>
{hasDuplicate && (
<div className="mb-2 flex items-start rounded-md border border-yellow-600 bg-yellow-900/20 px-3 py-2">
<div className="flex text-sm text-yellow-100">
<FontAwesomeIcon icon={faWarning} className="mt-1 mr-2 text-yellow-600" />
<div>
<p>
Another secret sync in your organization is already configured with the same
destination. This may lead to conflicts or unexpected behavior.
</p>
{duplicateProjectId && (
<p className="mt-1 text-xs text-yellow-200">
Duplicate found in project ID:{" "}
<code className="rounded-sm bg-yellow-800/50 px-1 py-0.5">
{duplicateProjectId}
</code>
</p>
)}
</div>
</div>
</div>
)}
<div className="flex flex-wrap gap-x-8 gap-y-2">
<GenericFieldLabel label="Connection">{connection.name}</GenericFieldLabel>
{DestinationFieldsComponent}
@@ -84,7 +84,7 @@ export const SecretReferenceNode = ({
<Collapsible.Trigger
className={twMerge(
hasChildren && "decoration-bunker-4ø00 underline-offset-4 data-[state=open]:underline",
"[&>svg]:data-[state=open]:rotate-[90deg] [&>svg]:data-[state=open]:text-yellow-500"
"data-[state=open]:[&>svg]:rotate-90 data-[state=open]:[&>svg]:text-yellow-500"
)}
disabled={!hasChildren}
>
@@ -201,7 +201,7 @@ export const SecretReferenceTree = ({ secretPath, environment, secretKey }: Prop
/>
</FormControl>
<FormLabel className="mb-2" label="Reference Tree" />
<div className="thin-scrollbar relative max-h-96 overflow-auto rounded-md border border-mineshaft-600 bg-bunker-700 py-6 text-sm text-mineshaft-200">
<div className="relative max-h-96 thin-scrollbar overflow-auto rounded-md border border-mineshaft-600 bg-bunker-700 py-6 text-sm text-mineshaft-200">
{isError ? (
<div className="flex items-center justify-center py-4">
<FontAwesomeIcon icon={faExclamationTriangle} className="mr-2 text-red-500" />
@@ -170,13 +170,13 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
Tag Color
</div>
<div className="flex space-x-2">
<div className="flex items-center justify-center rounded border border-mineshaft-500 bg-mineshaft-900 p-2">
<div className="flex items-center justify-center rounded-sm border border-mineshaft-500 bg-mineshaft-900 p-2">
<div
className="h-6 w-6 rounded-full"
style={{ background: `${selectedTagColor}` }}
/>
</div>
<div className="flex flex-grow items-center rounded border-mineshaft-500 bg-mineshaft-900 px-1 pr-2">
<div className="flex grow items-center rounded-sm border-mineshaft-500 bg-mineshaft-900 px-1 pr-2">
{!showHexInput ? (
<div className="inline-flex items-center gap-3 pl-3">
{secretTagsColors.map(($tagColor: TagColor) => {
@@ -202,7 +202,7 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
})}
</div>
) : (
<div className="tags-hex-wrapper flex flex-grow items-center px-2">
<div className="tags-hex-wrapper flex grow items-center px-2">
<div className="relative flex items-center rounded-md">
{isValidHexColor(selectedTagColor) && (
<div
@@ -216,7 +216,7 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
<div className="bg-blue h-7 w-7 rounded-full border border-dashed border-mineshaft-500" />
)}
</div>
<div className="flex-grow">
<div className="grow">
<Input
variant="plain"
value={selectedTagColor}
@@ -230,7 +230,7 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
<div className="mx-4 h-8 border border-mineshaft-500" />
<div className="flex h-7 w-7 items-center justify-center">
<div
className={`flex h-7 w-7 cursor-pointer items-center justify-center rounded-sm border border-mineshaft-500 bg-mineshaft-900 bg-transparent p-2 hover:ring-2 hover:ring-offset-1 ${
className={`flex h-7 w-7 cursor-pointer items-center justify-center rounded-xs border border-mineshaft-500 bg-mineshaft-900 bg-transparent p-2 hover:ring-2 hover:ring-offset-1 ${
showHexInput ? "tags-conic-bg rounded-full" : ""
}`}
onClick={() => setShowHexInput((prev) => !prev)}
@@ -16,7 +16,7 @@ export const AccessRestrictedBanner = ({
)
}: Props) => {
return (
<div className="flex items-center rounded-md border border-mineshaft-500 bg-gradient-to-br from-mineshaft-900 to-mineshaft-600 px-16 py-12 text-center text-bunker-300">
<div className="flex items-center rounded-md border border-mineshaft-500 bg-linear-to-br from-mineshaft-900 to-mineshaft-600 px-16 py-12 text-center text-bunker-300">
<div>
<div className="text-4xl font-medium text-bunker-100">{title}</div>
<div className="-mt-1 text-sm">{body}</div>
@@ -27,7 +27,7 @@ export const AccordionTrigger = forwardRef<
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
className={twMerge(
"group flex h-11 flex-1 items-center justify-between px-4 py-2 outline-none hover:text-primary data-[state=open]:text-primary",
"group flex h-11 flex-1 items-center justify-between px-4 py-2 outline-hidden hover:text-primary data-[state=open]:text-primary",
className
)}
{...props}
@@ -36,7 +36,7 @@ export const AccordionTrigger = forwardRef<
{children}
<FontAwesomeIcon
icon={faChevronDown}
className="text-sm transition-transform duration-300 ease-[cubic-bezier(0.87,_0,_0.13,_1)] group-data-[state=open]:rotate-180"
className="text-sm transition-transform duration-300 ease-[cubic-bezier(0.87,0,0.13,1)] group-data-[state=open]:rotate-180"
aria-hidden
/>
</AccordionPrimitive.Trigger>
@@ -53,7 +53,7 @@ export const AccordionContent = forwardRef<
>(({ children, className, childrenClassName, ...props }, forwardedRef) => (
<AccordionPrimitive.Content
className={twMerge(
"overflow-hidden data-[state=closed]:animate-slideUp data-[state=open]:animate-slideDown",
"data-[state=closed]:animate-slide-up data-[state=open]:animate-slide-down overflow-hidden",
className
)}
{...props}
+1 -1
View File
@@ -66,7 +66,7 @@ const Alert = forwardRef<
</div>
<div className="flex flex-col gap-y-1">
{hideTitle ? null : (
<h5 className="font-medium leading-6 tracking-tight" {...props}>
<h5 className="leading-6 font-medium tracking-tight" {...props}>
{defaultTitle}
</h5>
)}
+1 -1
View File
@@ -10,7 +10,7 @@ interface IProps {
const badgeVariants = cva(
[
"inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-xs text-yellow opacity-80 hover:opacity-100"
"inline-block cursor-default rounded-md bg-yellow/20 px-1.5 py-0.5 text-xs text-yellow opacity-80 hover:opacity-100"
],
{
variants: {
+2 -2
View File
@@ -11,11 +11,11 @@ export const Blur = ({ className, tooltipText }: IProps) => {
return (
<Tooltip content={tooltipText} className="max-w-md" isDisabled={!tooltipText}>
<div
className={twMerge("flex w-80 flex-grow items-center py-1 pl-4 pr-2", className)}
className={twMerge("flex w-80 grow items-center py-1 pr-2 pl-4", className)}
tabIndex={0}
role="button"
>
<span className="blur">********</span>
<span className="blur-sm">********</span>
</div>
</Tooltip>
);
@@ -27,7 +27,7 @@ const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWi
<ol
ref={ref}
className={twMerge(
"flex flex-wrap items-center break-words text-sm text-bunker-100",
"flex flex-wrap items-center text-sm break-words text-bunker-100",
className
)}
{...props}
+5 -5
View File
@@ -31,7 +31,7 @@ const buttonVariants = cva(
colorSchema: {
primary: ["bg-primary", "text-black", "border-primary bg-opacity-90 hover:bg-opacity-100"],
secondary: ["bg-mineshaft", "text-gray-300", "border-mineshaft hover:bg-opacity-80"],
danger: ["!bg-red", "!text-white", "!border-red hover:!bg-opacity-90"],
danger: ["bg-red!", "text-white!", "border-red! hover:!bg-opacity-90"],
gray: ["bg-bunker-500", "text-bunker-200"]
},
variant: {
@@ -42,7 +42,7 @@ const buttonVariants = cva(
outline_bg: "",
// a constant color not in use on hover or click goes colorSchema color
star: "text-bunker-200 bg-mineshaft-700 border-mineshaft-600",
link: "text-primary !p-0 bg-transparent outline-none border-none"
link: "text-primary p-0! bg-transparent outline-hidden border-none"
},
isDisabled: {
true: "bg-mineshaft-700 border border-mineshaft-600 text-white opacity-50 cursor-not-allowed",
@@ -84,7 +84,7 @@ const buttonVariants = cva(
colorSchema: "primary",
variant: "outline_bg",
className:
"bg-mineshaft-600 border border-mineshaft-500 hover:bg-primary/[0.1] hover:border-primary/40 text-bunker-200"
"bg-mineshaft-600 border border-mineshaft-500 hover:bg-primary/10 hover:border-primary/40 text-bunker-200"
},
{
colorSchema: "secondary",
@@ -116,7 +116,7 @@ const buttonVariants = cva(
colorSchema: "danger",
variant: "outline_bg",
className:
"bg-mineshaft-600 border border-red-500 hover:bg-red/[0.1] hover:border-red/40 text-red-500"
"bg-mineshaft-600 border border-red-500/40 hover:bg-red/15 bg-red/10 hover:border-red text-red-500"
},
{
colorSchema: "primary",
@@ -198,7 +198,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
: "infisical_loading"
}
isAutoPlay
className="absolute w-8 rounded-xl opacity-80 shadow-sm"
className="absolute w-8 rounded-xl opacity-80 shadow-xs"
/>
)}
{leftIcon && (
+2 -2
View File
@@ -10,7 +10,7 @@ export type CardTitleProps = {
export const CardTitle = ({ children, className, subTitle }: CardTitleProps) => (
<div
className={twMerge(
"mb-5 break-words border-b border-mineshaft-600 px-6 py-4 font-sans text-lg font-normal",
"mb-5 border-b border-mineshaft-600 px-6 py-4 font-sans text-lg font-normal break-words",
className
)}
>
@@ -35,7 +35,7 @@ export type CardBodyProps = {
};
export const CardBody = ({ children, className, style }: CardBodyProps) => (
<div className={twMerge("px-6 pb-6 pt-0", className)} style={style}>
<div className={twMerge("px-6 pt-0 pb-6", className)} style={style}>
{children}
</div>
);
@@ -40,7 +40,7 @@ export const Checkbox = ({
<div className={twMerge("flex items-center font-inter text-bunker-300", containerClassName)}>
<CheckboxPrimitive.Root
className={twMerge(
"flex h-4 w-4 flex-shrink-0 items-center justify-center rounded border border-mineshaft-400/50 bg-mineshaft-600 shadow transition-all hover:bg-mineshaft-500",
"flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border border-mineshaft-400/50 bg-mineshaft-600 shadow-sm transition-all hover:bg-mineshaft-500",
isDisabled && "bg-bunker-400 hover:bg-bunker-400",
isChecked && "border-primary/50 bg-primary/30",
Boolean(children) && "mr-3",
@@ -89,7 +89,7 @@ export const ConfirmActionModal = ({
>
<FormControl
label={
<div className="break-words pb-2 text-sm">
<div className="pb-2 text-sm break-words">
{confirmationMessage || (
<>
Type <span className="font-bold">{confirmKey}</span> to perform this action
@@ -30,7 +30,7 @@ export const ContentLoader = ({ text, frequency = 2000, className, lottieClassNa
return (
<div
className={twMerge(
"container relative mx-auto flex h-screen w-full flex-col items-center justify-center space-y-8 px-8 text-mineshaft-50 dark:[color-scheme:dark]",
"relative container mx-auto flex h-screen w-full flex-col items-center justify-center space-y-8 px-8 text-mineshaft-50 dark:scheme-dark",
className
)}
>
@@ -42,9 +42,9 @@ export const CreatableSelect = <T,>({
),
placeholder: () => "text-mineshaft-400 text-sm pl-1 py-0.5",
input: () => "pl-1 py-0.5",
valueContainer: () => `p-1 max-h-[14rem] ${isMulti ? "!overflow-y-scroll" : ""} gap-1`,
valueContainer: () => `p-1 max-h-56 ${isMulti ? "overflow-y-scroll!" : ""} gap-1`,
singleValue: () => "leading-7 ml-1",
multiValue: () => "bg-mineshaft-600 rounded items-center py-0.5 px-2 gap-1.5",
multiValue: () => "bg-mineshaft-600 rounded-sm items-center py-0.5 px-2 gap-1.5",
multiValueLabel: () => "leading-6 text-sm",
multiValueRemove: () => "hover:text-red text-bunker-400",
indicatorsContainer: () => "p-1 gap-1",
@@ -120,7 +120,7 @@ export const DatePicker = ({
today: "text-primary border-primary",
selected: " text-mineshaft-100 bg-mineshaft-500",
root: `text-mineshaft-300 ${defaultClassNames}`,
[UI.DayButton]: "p-3 rounded hover:text-mineshaft-100",
[UI.DayButton]: "p-3 rounded-sm hover:text-mineshaft-100",
[UI.Weekday]: "px-3 pt-3",
[UI.Chevron]: "fill-mineshaft-300/70 hover:fill-mineshaft-300",
disabled: "text-mineshaft-400 pointer-events-none"
@@ -133,7 +133,7 @@ export const DatePicker = ({
type="time"
value={displayUtc ? localTimeToUTC(timeValue) : timeValue}
onChange={handleTimeChange}
className="bg-mineshaft-700 text-white [color-scheme:dark]"
className="bg-mineshaft-700 text-white scheme-dark"
/>
</div>
)}
@@ -96,7 +96,7 @@ export const DeleteActionModal = ({
>
<FormControl
label={
<div className="break-words pb-2 text-sm">
<div className="pb-2 text-sm break-words">
{deletionMessage || (
<>
Type <span className="font-bold">{deleteKey}</span> to perform this action
+4 -4
View File
@@ -17,14 +17,14 @@ export type DrawerContentProps = DialogPrimitive.DialogContentProps & {
} & VariantProps<typeof drawerContentVariation>;
const drawerContentVariation = cva(
"fixed ease-in-out duration-300 z-[90] border border-mineshaft-600 drop-shadow-2xl",
"fixed ease-in-out duration-300 z-90 border border-mineshaft-600 drop-shadow-2xl",
{
variants: {
direction: {
right: [
"right-0 top-0",
"h-full w-96",
"data-[state=open]:animate-drawerRightIn data-[state=closed]:animate-drawerRightOut"
"data-[state=open]:animate-drawer-right-in data-[state=closed]:animate-drawer-right-out"
]
}
}
@@ -64,7 +64,7 @@ export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
)}
<CardBody
className={twMerge(
"flex-grow overflow-y-auto overflow-x-hidden px-4 pt-4 dark:[color-scheme:dark]",
"grow overflow-x-hidden overflow-y-auto px-4 pt-4 dark:scheme-dark",
cardBodyClassName
)}
>
@@ -75,7 +75,7 @@ export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
<IconButton
variant="plain"
ariaLabel="close"
className="absolute right-6 top-4 rounded text-bunker-400 hover:text-bunker-50"
className="absolute top-4 right-6 rounded-sm text-bunker-400 hover:text-bunker-50"
>
<FontAwesomeIcon icon={faTimes} size="lg" className="cursor-pointer" />
</IconButton>
@@ -24,7 +24,7 @@ export const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuConten
{...props}
ref={forwardedRef}
className={twMerge(
"z-30 min-w-[220px] overflow-y-auto rounded-md border border-mineshaft-600 bg-mineshaft-900 text-bunker-300 shadow will-change-auto data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade data-[side=right]:animate-slideLeftAndFade data-[side=top]:animate-slideDownAndFade",
"data-[side=bottom]:animate-slide-up-and-fade data-[side=left]:animate-slide-right-and-fade data-[side=right]:animate-slide-left-and-fade data-[side=top]:animate-slide-down-and-fade z-30 min-w-[220px] overflow-y-auto rounded-md border border-mineshaft-600 bg-mineshaft-900 text-bunker-300 shadow-sm will-change-auto",
className
)}
>
@@ -48,7 +48,7 @@ export const DropdownSubMenuContent = forwardRef<HTMLDivElement, DropdownSubMenu
{...props}
ref={forwardedRef}
className={twMerge(
"z-30 min-w-[220px] rounded-md border border-mineshaft-600 bg-mineshaft-900 text-bunker-300 shadow will-change-auto data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade data-[side=right]:animate-slideLeftAndFade data-[side=top]:animate-slideDownAndFade",
"data-[side=bottom]:animate-slide-up-and-fade data-[side=left]:animate-slide-right-and-fade data-[side=right]:animate-slide-left-and-fade data-[side=top]:animate-slide-down-and-fade z-30 min-w-[220px] rounded-md border border-mineshaft-600 bg-mineshaft-900 text-bunker-300 shadow-sm will-change-auto",
className
)}
>
@@ -66,7 +66,7 @@ export type DropdownLabelProps = DropdownMenuPrimitive.DropdownMenuLabelProps;
export const DropdownMenuLabel = ({ className, ...props }: DropdownLabelProps) => (
<DropdownMenuPrimitive.Label
{...props}
className={twMerge("px-4 pb-1 pt-2 text-xs text-bunker-400", className)}
className={twMerge("px-4 pt-2 pb-1 text-xs text-bunker-400", className)}
/>
);
@@ -92,14 +92,14 @@ export const DropdownMenuItem = <T extends ElementType = "button">({
<DropdownMenuPrimitive.Item
{...props}
className={twMerge(
"block cursor-pointer rounded-sm px-4 py-2 font-inter text-xs text-mineshaft-200 outline-none data-[highlighted]:bg-mineshaft-700",
"block cursor-pointer rounded-xs px-4 py-2 font-inter text-xs text-mineshaft-200 outline-hidden data-highlighted:bg-mineshaft-700",
className,
isDisabled ? "pointer-events-none cursor-not-allowed opacity-50" : ""
)}
>
<Item type="button" role="menuitem" className="flex w-full items-center" ref={inputRef}>
{icon && iconPos === "left" && <span className="mr-2 flex items-center">{icon}</span>}
<span className="flex-grow text-left">{children}</span>
<span className="grow text-left">{children}</span>
{icon && iconPos === "right" && <span className="ml-2 flex items-center">{icon}</span>}
</Item>
</DropdownMenuPrimitive.Item>
@@ -126,13 +126,13 @@ export const DropdownSubMenuTrigger = <T extends ElementType = "button">({
<DropdownMenuPrimitive.SubTrigger
{...props}
className={twMerge(
"block cursor-pointer rounded-sm px-4 py-2 font-inter text-xs text-mineshaft-200 outline-none data-[highlighted]:bg-mineshaft-700",
"block cursor-pointer rounded-xs px-4 py-2 font-inter text-xs text-mineshaft-200 outline-hidden data-highlighted:bg-mineshaft-700",
className
)}
>
<Item type="button" role="menuitem" className="flex w-full items-center" ref={inputRef}>
{icon && iconPos === "left" && <span className="mr-2 flex items-center">{icon}</span>}
<span className="flex-grow text-left">{children}</span>
<span className="grow text-left">{children}</span>
{icon && iconPos === "right" && <span className="ml-2 flex items-center">{icon}</span>}
</Item>
</DropdownMenuPrimitive.SubTrigger>
@@ -161,7 +161,7 @@ export const DropdownMenuSeparator = forwardRef<
<DropdownMenuPrimitive.Separator
ref={ref}
{...props}
className={twMerge("m-1 h-[1px] bg-gray-700", className)}
className={twMerge("m-1 h-px bg-gray-700", className)}
/>
));
+2 -2
View File
@@ -21,7 +21,7 @@ function onError(error: Error) {
}
const inputVariants = cva(
"input w-full py-[0.375rem] text-gray-400 placeholder:text-sm placeholder-gray-500 placeholder-opacity-50 outline-none focus:ring-2 hover:ring-bunker-400/60 duration-100",
"input w-full py-1.5 text-gray-400 placeholder:text-sm placeholder-gray-500/50 outline-hidden focus:ring-2 hover:ring-bunker-400/60 duration-100",
{
variants: {
size: {
@@ -37,7 +37,7 @@ const inputVariants = cva(
variant: {
filled: ["bg-mineshaft-900", "text-gray-400"],
outline: ["bg-transparent"],
plain: "bg-transparent outline-none"
plain: "bg-transparent outline-hidden"
},
isError: {
true: "focus:ring-red/50 placeholder-red-300",
@@ -81,12 +81,12 @@ export const FilterableSelect = <T,>({
}}
classNames={{
container: ({ isDisabled }) =>
twMerge("w-full font-inter text-sm", isDisabled && "!pointer-events-auto opacity-50"),
twMerge("w-full font-inter text-sm", isDisabled && "pointer-events-auto! opacity-50"),
control: ({ isFocused, isDisabled }) =>
twMerge(
isFocused ? "border-primary-400/50" : "border-mineshaft-600",
`w-full rounded-md border bg-mineshaft-900 p-0.5 font-inter text-mineshaft-200 ${
isDisabled ? "!cursor-not-allowed" : "hover:cursor-pointer hover:border-gray-400"
isDisabled ? "cursor-not-allowed!" : "hover:cursor-pointer hover:border-gray-400"
} `
),
placeholder: () =>
@@ -94,10 +94,10 @@ export const FilterableSelect = <T,>({
input: () => `pl-1 ${isMulti ? "py-[0.22rem]" : ""}`,
valueContainer: () =>
`px-1 max-h-[8.2rem] ${
isMulti ? "!overflow-y-auto thin-scrollbar py-1" : "py-[0.1rem]"
isMulti ? "overflow-y-auto! thin-scrollbar py-1" : "py-[0.1rem]"
} gap-1`,
singleValue: () => "leading-7 ml-1",
multiValue: () => "bg-mineshaft-600 text-sm rounded items-center py-0.5 px-2 gap-1.5",
multiValue: () => "bg-mineshaft-600 text-sm rounded-sm items-center py-0.5 px-2 gap-1.5",
multiValueLabel: () => "leading-6 text-sm",
multiValueRemove: () => "hover:text-red text-bunker-400",
indicatorsContainer: () => "p-1 gap-1",
@@ -112,7 +112,7 @@ export const FilterableSelect = <T,>({
twMerge(
isFocused && "bg-mineshaft-700 active:bg-mineshaft-600",
isSelected && "text-mineshaft-200",
"rounded px-3 py-2 text-xs hover:cursor-pointer"
"rounded-sm px-3 py-2 text-xs hover:cursor-pointer"
),
noOptionsMessage: () => "text-mineshaft-400 p-2 rounded-md",
loadingMessage: () => "text-mineshaft-400 p-2 rounded-md"
@@ -8,7 +8,7 @@ export const FontAwesomeSymbol = forwardRef<HTMLDivElement, Props>(
({ symbolName, ...props }, ref) => {
return (
<div ref={ref} {...props}>
<svg className="w-inherit h-inherit">
<svg className="h-inherit w-inherit">
<use href={`#${symbolName}`} />
</svg>
</div>
@@ -36,7 +36,7 @@ export const FormLabel = ({
>
{label}
{isRequired && <span className="ml-1 text-red">*</span>}
{isOptional && <span className="ml-1 text-xs italic text-gray-500">- Optional</span>}
{isOptional && <span className="ml-1 text-xs text-gray-500 italic">- Optional</span>}
{icon && !tooltipText && (
<span className="ml-2 cursor-default text-mineshaft-300 hover:text-mineshaft-200">
{icon}
@@ -58,7 +58,7 @@ export type FormHelperTextProps = {
export const FormHelperText = ({ isError, text }: FormHelperTextProps) => (
<div
className={twMerge(
"mt-2 flex items-center font-inter text-xs text-mineshaft-300 text-opacity-90",
"text-opacity-90 mt-2 flex items-center font-inter text-xs text-mineshaft-300",
isError && "text-red-600"
)}
>

Some files were not shown because too many files have changed in this diff Show More