Merge remote-tracking branch 'origin/main' into feat/ENG-3443

This commit is contained in:
Carlos Monastyrski
2025-12-08 10:22:30 -03:00
1142 changed files with 33462 additions and 13730 deletions
+7 -7
View File
@@ -8,15 +8,15 @@
http-equiv="Content-Security-Policy"
content="
default-src 'self';
connect-src 'self' https://*.posthog.com http://127.0.0.1:* https://cdn.jsdelivr.net/npm/@lottiefiles/[email protected]/dist/dotlottie-player.wasm;
script-src 'self' https://*.posthog.com https://js.stripe.com https://api.stripe.com https://widget.intercom.io https://js.intercomcdn.com https://hcaptcha.com https://*.hcaptcha.com 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net/npm/@lottiefiles/[email protected]/dist/dotlottie-player.wasm;
style-src 'self' 'unsafe-inline' https://hcaptcha.com https://*.hcaptcha.com;
connect-src 'self' https://d1zwf0dwl0k2ky.cloudfront.net https://*.posthog.com http://127.0.0.1:* https://cdn.jsdelivr.net/npm/@lottiefiles/[email protected]/dist/dotlottie-player.wasm;
script-src 'self' https://d1zwf0dwl0k2ky.cloudfront.net https://*.posthog.com https://js.stripe.com https://api.stripe.com https://widget.intercom.io https://js.intercomcdn.com https://hcaptcha.com https://*.hcaptcha.com 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net/npm/@lottiefiles/[email protected]/dist/dotlottie-player.wasm;
style-src 'self' https://d1zwf0dwl0k2ky.cloudfront.net 'unsafe-inline' https://hcaptcha.com https://*.hcaptcha.com;
child-src https://api.stripe.com;
frame-src https://js.stripe.com/ https://api.stripe.com https://www.youtube.com/ https://hcaptcha.com https://*.hcaptcha.com;
connect-src 'self' wss://nexus-websocket-a.intercom.io https://api-iam.intercom.io https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://api.stripe.com https://api.pwnedpasswords.com http://127.0.0.1:* https://hcaptcha.com https://*.hcaptcha.com;
img-src 'self' https://static.intercomassets.com https://js.intercomcdn.com https://downloads.intercomcdn.com https://*.stripe.com https://i.ytimg.com/ data:;
media-src https://js.intercomcdn.com;
font-src 'self' https://fonts.intercomcdn.com/ https://fonts.gstatic.com;
connect-src 'self' https://d1zwf0dwl0k2ky.cloudfront.net wss://nexus-websocket-a.intercom.io https://api-iam.intercom.io https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://api.stripe.com https://api.pwnedpasswords.com http://127.0.0.1:* https://hcaptcha.com https://*.hcaptcha.com;
img-src 'self' https://d1zwf0dwl0k2ky.cloudfront.net https://static.intercomassets.com https://js.intercomcdn.com https://downloads.intercomcdn.com https://*.stripe.com https://i.ytimg.com/ data:;
media-src https://d1zwf0dwl0k2ky.cloudfront.net https://js.intercomcdn.com;
font-src 'self' https://d1zwf0dwl0k2ky.cloudfront.net https://fonts.intercomcdn.com/ https://fonts.gstatic.com;
"
/>
<title>Infisical</title>
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 120 60" width="120" height="60" style="enable-background:new 0 0 120 60;" xml:space="preserve">
<style type="text/css">
.st0{fill:#808285;}
.st1{fill:url(#SVGID_1_);}
.st2{fill:#005B99;}
.st3{enable-background:new ;}
.st4{fill:#77787B;}
</style>
<g>
<path class="st0" d="M34.1,42.6h9.4v-3.2C41.5,41.2,38.2,42.3,34.1,42.6L34.1,42.6z"/>
<path class="st0" d="M17.3,40.1v2.5h11.9c-2.4-0.2-5-0.6-7.6-1.2C20.1,41,18.7,40.6,17.3,40.1L17.3,40.1z"/>
<radialGradient id="SVGID_1_" cx="-183.5882" cy="811.1324" r="47.498" gradientTransform="matrix(1 0 0 1 241.0864 -776.3726)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#0095DA"/>
<stop offset="0.21" style="stop-color:#0095DA"/>
<stop offset="0.33" style="stop-color:#00ACE4"/>
<stop offset="0.9045" style="stop-color:#005093"/>
<stop offset="0.9335" style="stop-color:#005396"/>
<stop offset="0.954" style="stop-color:#005C9F"/>
<stop offset="0.9718" style="stop-color:#006BAE"/>
<stop offset="0.988" style="stop-color:#0080C4"/>
<stop offset="1" style="stop-color:#0095DA"/>
</radialGradient>
<path class="st1" d="M43.5,18.5H29.2C23,17.1,15.4,17.1,10,18.2c2.4-0.3,5.1-0.3,7.8,0.1c0.7,0.1,1.3,0.2,2,0.3l0,0
c2.9,0.5,5.7,1.2,8.1,2.2l0,0c0.3,0.1,0.5,0.2,0.8,0.3h0c0.1,0,0.2,0.1,0.3,0.1h0c0.1,0,0.2,0.1,0.3,0.1h0c0.1,0,0.2,0.1,0.2,0.1
l0,0c0.1,0,0.1,0.1,0.2,0.1l0,0c0.1,0,0.1,0.1,0.2,0.1l0,0c0.1,0,0.2,0.1,0.2,0.1l0,0l0,0h0c0.1,0,0.1,0.1,0.2,0.1l0,0
c0.1,0,0.1,0.1,0.2,0.1l0,0c0.1,0,0.1,0.1,0.2,0.1l0,0c0.1,0,0.1,0.1,0.2,0.1l0,0c0.1,0,0.1,0.1,0.2,0.1l0,0c0.1,0,0.1,0.1,0.2,0.1
l0.1,0c0.1,0,0.1,0.1,0.2,0.1l0.1,0c0.1,0,0.1,0.1,0.2,0.1l0.1,0c0.1,0,0.1,0.1,0.2,0.1l0.1,0c0.1,0,0.1,0.1,0.1,0.1l0.1,0.1
c0.1,0,0.1,0.1,0.1,0.1l0.1,0.1c0.1,0,0.1,0.1,0.1,0.1l0.1,0.1c0,0,0.1,0.1,0.1,0.1c3.2,2.2,5.1,4.9,5.1,7.6c0,3.1-2.6,5.7-6.8,7.2
c2.9-1.5,4.6-3.6,4.6-6.1c0-3.1-2.7-6.3-7-8.7c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1
c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1l-0.2-0.1c-0.1,0-0.2-0.1-0.2-0.1
l-0.1-0.1c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1c-0.1,0-0.1-0.1-0.2-0.1
L26,22c-0.1,0-0.2-0.1-0.3-0.1h0l-0.2-0.1l0,0c-0.1,0-0.2-0.1-0.3-0.1l-0.1,0c-0.1-0.1-0.2-0.1-0.4-0.1h0c-0.1,0-0.2-0.1-0.3-0.1h0
c-0.1-0.1-0.3-0.1-0.5-0.2l-0.1,0c-0.1-0.1-0.3-0.1-0.5-0.1h0c-0.3-0.1-0.5-0.2-0.8-0.3l0,0c-0.3-0.1-0.6-0.2-0.8-0.3l0,0
c-0.2-0.1-0.3-0.1-0.5-0.1l0,0c-0.7-0.2-1.5-0.4-2.3-0.5l0,0c-0.6-0.1-1.2-0.2-1.8-0.3v19.2c0.2,0.1,0.4,0.1,0.6,0.1
C30.3,42,41,39.4,41.8,33.2c0.4-3.3-2-6.9-6.1-10c3.3,1.9,6,4.1,7.8,6.4c1,1.4,1.7,2.7,2,4.1c-0.1-1.9-0.9-4.8-2-6.9L43.5,18.5
L43.5,18.5z"/>
<g>
<path class="st2" d="M67.5,26.9c0,1-0.2,2-0.7,2.9c-0.4,0.9-1,1.7-1.8,2.3c-0.8,0.7-1.7,1.2-2.8,1.6c-1.1,0.4-2.2,0.6-3.5,0.6H49
v-9h4.5v5.3h5.2c0.6,0,1.2-0.1,1.7-0.3c0.5-0.2,1-0.4,1.4-0.7c0.4-0.3,0.7-0.7,0.9-1.1c0.2-0.4,0.3-0.9,0.3-1.4
c0-0.5-0.1-1-0.3-1.4c-0.2-0.4-0.5-0.8-0.9-1.1c-0.4-0.3-0.8-0.6-1.4-0.7c-0.5-0.2-1.1-0.3-1.7-0.3H49l2.9-3.8h6.8
c1.3,0,2.4,0.2,3.5,0.5c1.1,0.3,2,0.8,2.8,1.5s1.4,1.4,1.8,2.3C67.2,24.9,67.5,25.8,67.5,26.9z"/>
<g>
<path class="st2" d="M82.8,21.4v6.8l-8.9-8c-0.4-0.3-0.7-0.5-1-0.6c-0.3-0.1-0.6-0.1-0.8-0.1c-0.3,0-0.6,0.1-0.9,0.1
c-0.3,0.1-0.6,0.3-0.8,0.5c-0.2,0.2-0.4,0.5-0.5,0.8c-0.1,0.3-0.2,0.8-0.2,1.2v12h4.1v-8.5l8.9,8c0.3,0.3,0.7,0.5,0.9,0.6
c0.3,0.1,0.6,0.1,0.8,0.1c0.3,0,0.6-0.1,0.9-0.1c0.3-0.1,0.6-0.3,0.8-0.5c0.2-0.2,0.4-0.5,0.5-0.8c0.1-0.3,0.2-0.8,0.2-1.2V19.9
L82.8,21.4z"/>
</g>
<path class="st2" d="M103,25.5c1.8,0,3.1,0.3,4,1c0.9,0.7,1.4,1.6,1.4,3c0,0.7-0.1,1.4-0.3,2c-0.2,0.6-0.6,1.1-1.1,1.5
c-0.5,0.4-1.2,0.7-1.9,0.9c-0.8,0.2-1.7,0.3-2.8,0.3H88.7l2.9-3.7h11c0.5,0,0.9-0.1,1.2-0.3c0.3-0.2,0.4-0.4,0.4-0.8
s-0.1-0.7-0.4-0.8c-0.3-0.2-0.6-0.2-1.2-0.2h-7.9c-0.9,0-1.8-0.1-2.4-0.3c-0.7-0.2-1.2-0.5-1.7-0.8c-0.5-0.4-0.8-0.8-1-1.3
c-0.2-0.5-0.3-1.1-0.3-1.7c0-0.7,0.1-1.3,0.4-1.9c0.2-0.6,0.6-1,1.1-1.4c0.5-0.4,1.1-0.7,1.9-0.9c0.8-0.2,1.7-0.3,2.8-0.3h12.6
l-2.9,3.8H95.1c-0.5,0-0.9,0.1-1.2,0.2c-0.3,0.1-0.4,0.4-0.4,0.8c0,0.4,0.1,0.6,0.4,0.8c0.3,0.1,0.6,0.2,1.2,0.2L103,25.5
L103,25.5z"/>
</g>
<g class="st3">
<path class="st4" d="M57.5,36.6v5h-1.4v-2.7v-0.7l0.1-0.4v-0.4h-0.1l-0.2,0.3l-0.2,0.3l-0.4,0.7l-1.7,2.8h-1.3l-1.7-2.8l-0.4-0.7
l-0.2-0.3l-0.2-0.3h-0.1l0,0.4v0.4l0.1,0.7v2.7h-1.5v-5h2.4l1.4,2.3l0.4,0.7l0.2,0.3l0.2,0.3H53l0.2-0.3l0.2-0.3l0.4-0.7l1.4-2.3
L57.5,36.6L57.5,36.6z"/>
<path class="st4" d="M63.6,40.6h-3.3l-0.5,0.9h-1.6l2.6-5H63l2.6,5H64L63.6,40.6z M63.2,39.9l-1.3-2.6l-1.3,2.6H63.2z"/>
<path class="st4" d="M66.2,41.6v-5H70c1,0,1.8,0.2,2.2,0.5s0.7,0.8,0.7,1.6c0,0.7,0,1.2-0.1,1.5c0,0.3-0.2,0.6-0.5,0.9
c-0.3,0.3-1,0.5-2.3,0.5H66.2L66.2,41.6z M67.7,40.7h2.1c0.7,0,1.2-0.1,1.4-0.3s0.3-0.7,0.3-1.4c0-0.7-0.1-1.2-0.3-1.4
s-0.6-0.3-1.3-0.3h-2.2L67.7,40.7L67.7,40.7z"/>
<path class="st4" d="M75.3,37.4v1.3h3.6v0.7h-3.6v1.4h3.8v0.8h-5.3v-5h5.3v0.8L75.3,37.4L75.3,37.4z"/>
<path class="st4" d="M84.4,37.4v1.3H88v0.7h-3.6v1.4h3.8v0.8H83v-5h5.3v0.8L84.4,37.4L84.4,37.4z"/>
<path class="st4" d="M94,40.6h-3.3l-0.5,0.9h-1.6l2.6-5h2.2l2.6,5h-1.5L94,40.6z M93.7,39.9l-1.3-2.6L91,39.9H93.7z"/>
<path class="st4" d="M102.4,38.1H101v-0.1c0-0.3-0.1-0.4-0.3-0.5c-0.2-0.1-0.6-0.1-1.2-0.1c-0.7,0-1.2,0-1.4,0.1
c-0.2,0.1-0.3,0.3-0.3,0.5c0,0.3,0.1,0.5,0.3,0.6s0.8,0.1,1.7,0.1c1.1,0,1.9,0.1,2.2,0.3c0.3,0.2,0.5,0.5,0.5,1.1
c0,0.7-0.2,1.1-0.6,1.3s-1.2,0.3-2.5,0.3c-1.3,0-2.2-0.1-2.5-0.3c-0.4-0.2-0.6-0.6-0.6-1.2V40h1.4v0.1c0,0.3,0.1,0.5,0.3,0.6
c0.2,0.1,0.7,0.1,1.5,0.1c0.7,0,1.1,0,1.2-0.1s0.3-0.3,0.3-0.6c0-0.3-0.1-0.4-0.2-0.5c-0.1-0.1-0.4-0.1-0.9-0.1l-0.8,0
c-1.2,0-2-0.1-2.3-0.3c-0.4-0.2-0.5-0.6-0.5-1.1c0-0.6,0.2-1,0.6-1.2c0.4-0.2,1.2-0.3,2.5-0.3c1.1,0,1.9,0.1,2.3,0.3
c0.4,0.2,0.6,0.5,0.6,1L102.4,38.1L102.4,38.1z"/>
<path class="st4" d="M110,36.6l-2.9,3.1v1.9h-1.5v-1.9l-2.8-3.1h1.7l1.2,1.3l0.3,0.4l0.2,0.2l0.2,0.2l0.2-0.2l0.2-0.2l0.3-0.4
l1.2-1.3H110z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

+1 -2
View File
@@ -41,7 +41,6 @@
"common": {
"head-title": "{{title}} | Infisical",
"error_project-already-exists": "A project with this name already exists.",
"no-mobile": " To use Infisical, please log in through a device with larger dimensions. ",
"email": "Email",
"password": "Password",
"first-name": "First Name",
@@ -290,7 +289,7 @@
}
},
"project": {
"title": "Settings",
"title": "Project Settings",
"description": "These settings only apply to the currently selected Project.",
"danger-zone": "Danger Zone",
"delete-project": "Delete Project",
@@ -41,7 +41,6 @@
"common": {
"head-title": "{{title}} | Infisical",
"error_project-already-exists": "Ya existe un proyecto con este nombre.",
"no-mobile": "Para usar Infisical, inicia sesión con un dispositivo de mayores dimesiones.",
"email": "Correo electrónico",
"password": "Contraseña",
"first-name": "Nombre",
@@ -41,7 +41,6 @@
"common": {
"head-title": "{{title}} | Infisical",
"error_project-already-exists": "Un projet avec ce nom existe déjà.",
"no-mobile": " Pour utiliser Infisical, veuillez vous connecter avec un appareil avec des dimensions plus grandes. ",
"email": "Email",
"password": "Mot de passe",
"first-name": "Prénom",
@@ -30,7 +30,6 @@
"common": {
"head-title": "{{title}} | Infisical",
"error_project-already-exists": "동일한 이름을 가진 프로젝트가 이미 존재해요.",
"no-mobile": " Infisical을 사용하려면, 큰 화면을 가진 디바이스로 로그인하여 주세요.",
"email": "메일",
"password": "비밀번호",
"first-name": "이름",
@@ -41,7 +41,6 @@
"common": {
"head-title": "{{title}} | Infisical",
"error_project-already-exists": "Já exite um projeto com este nome.",
"no-mobile": "Para usar o Infisical, faça o login através de um dispositivo com dimensões maiores.",
"email": "Email",
"password": "Senha",
"first-name": "Primeiro Nome",
@@ -41,7 +41,6 @@
"common": {
"head-title": "{{title}} | Infisical",
"error_project-already-exists": "Bu isimle bir proje zaten mevcut.",
"no-mobile": " Infisical'ı kullanmak için, lütfen daha büyük boyutlara sahip bir cihaz üzerinden giriş yapın. ",
"email": "Email",
"password": "Şifre",
"first-name": "Adınız",
@@ -20,9 +20,14 @@ export default function TeamInviteStep(): JSX.Element {
const { mutateAsync } = useAddUsersToOrg();
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["setUpEmail"] as const);
const orgId = String(localStorage.getItem("orgData.id"));
// Redirect user to the getting started page
const redirectToHome = async () => {
navigate({ to: "/organization/projects" as const });
navigate({
to: orgId ? ("/organizations/$orgId/projects" as const) : "/",
params: { orgId }
});
};
const inviteUsers = async ({ emails: inviteEmails }: { emails: string }) => {
@@ -32,7 +37,7 @@ export default function TeamInviteStep(): JSX.Element {
.map(async (email) => {
mutateAsync({
inviteeEmails: [email],
organizationId: String(localStorage.getItem("orgData.id")),
organizationId: orgId,
organizationRoleSlug: "member"
});
});
@@ -70,7 +70,8 @@ export default function NavHeader({
{currentOrg?.name?.charAt(0)}
</div>
<Link
to="/organization/projects"
to="/organizations/$orgId/projects"
params={{ orgId: currentOrg.id }}
className="truncate pl-0.5 text-sm font-medium text-primary/80 hover:text-primary"
>
{currentOrg?.name}
@@ -90,8 +91,8 @@ export default function NavHeader({
<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 }}
to="/organizations/$orgId/projects/secret-management/$projectId/overview"
params={{ orgId: currentOrg?.id || "", projectId: currentProject.id }}
className="text-sm font-medium text-primary/80 hover:text-primary"
>
{pageName}
@@ -126,8 +127,8 @@ export default function NavHeader({
<div className="flex items-center space-x-3">
<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 }}
to="/organizations/$orgId/projects/secret-management/$projectId/secrets/$envSlug"
params={{ orgId: currentOrg.id, projectId: currentProject.id, envSlug: routerEnvSlug }}
className="text-sm font-medium text-primary/80 hover:text-primary"
>
{userAvailableEnvs?.find(({ slug }) => slug === currentEnv)?.name}
@@ -188,8 +189,9 @@ export default function NavHeader({
</div>
) : (
<Link
to="/projects/secret-management/$projectId/secrets/$envSlug"
to="/organizations/$orgId/projects/secret-management/$projectId/secrets/$envSlug"
params={{
orgId: currentOrg?.id || "",
projectId: currentProject.id,
envSlug: routerEnvSlug || ""
}}
@@ -3,6 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import { useOrganization } from "@app/context";
import { useTimedReset } from "@app/hooks";
import { createNotification } from "../notifications";
@@ -23,6 +24,7 @@ export const SecretDashboardPathBreadcrumb = ({
projectId,
disableCopy
}: Props) => {
const { currentOrg } = useOrganization();
const [, isCopying, setIsCopying] = useTimedReset({
initialState: false
});
@@ -69,8 +71,9 @@ export const SecretDashboardPathBreadcrumb = ({
</div>
) : (
<Link
to="/projects/secret-management/$projectId/secrets/$envSlug"
to="/organizations/$orgId/projects/secret-management/$projectId/secrets/$envSlug"
params={{
orgId: currentOrg.id,
projectId,
envSlug: environmentSlug
}}
@@ -57,7 +57,8 @@ export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) =>
});
navigate({
to: "/organization/projects"
to: "/organizations/$orgId/projects",
params: { orgId: organization.id }
});
localStorage.setItem("orgData.id", organization.id);
@@ -87,17 +87,24 @@ const shouldShowConditionalAccess = (
folderPath: string,
conditionalFields: string[]
): boolean => {
return actionRuleMap.some((rule) => {
// Find all rules that apply to this environment/path
const applicableRules = actionRuleMap.filter((rule) => {
const ruleConditions = rule[action]?.conditions;
if (!ruleConditions) return false;
// Check if any of the conditional fields are present
const hasConditionalField = conditionalFields.some((field) => ruleConditions[field]);
if (!hasConditionalField) return false;
// Check if base conditions (environment and secretPath) apply
return doBaseConditionsApply(ruleConditions, environment, folderPath);
});
// If no rules apply, don't show conditional
if (applicableRules.length === 0) return false;
// Check if ALL applicable rules have conditional fields and if at least one rule applies without conditional fields, show full access
const allRulesHaveConditionalFields = applicableRules.every((rule) => {
const ruleConditions = rule[action]?.conditions;
if (!ruleConditions) return false;
return conditionalFields.some((field) => ruleConditions[field]);
});
return allRulesHaveConditionalFields;
};
const determineAccessLevel = (
@@ -64,7 +64,7 @@ export const CreatePkiSyncModal = ({
"Add Sync"
)
}
className="max-w-2xl"
className="max-w-3xl"
bodyClassName="overflow-visible"
subTitle={
selectedSync ? undefined : "Select a third-party service to sync certificates to."
@@ -15,11 +15,13 @@ type Props = {
export const EditPkiSyncModal = ({ pkiSync, onOpenChange, fields, ...props }: Props) => {
if (!pkiSync) return null;
const modalClassName = fields === PkiSyncEditFields.Mappings ? "max-w-4xl" : "max-w-2xl";
return (
<Modal {...props} onOpenChange={onOpenChange}>
<ModalContent
title={<PkiSyncModalHeader isConfigured destination={pkiSync.destination} />}
className="max-w-2xl"
className={modalClassName}
bodyClassName="overflow-visible"
>
<EditPkiSyncForm onComplete={() => onOpenChange(false)} fields={fields} pkiSync={pkiSync} />
@@ -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 AwsSecretsManagerPkiSyncFields = () => {
const { control, setValue } = useFormContext<
TPkiSyncForm & { destination: PkiSync.AwsSecretsManager }
>();
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 secrets will be stored in AWS Secrets Manager."
>
<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>
)}
/>
</>
);
};
@@ -0,0 +1,35 @@
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";
export const ChefPkiSyncFields = () => {
const { control, setValue } = useFormContext<TPkiSyncForm & { destination: PkiSync.Chef }>();
return (
<>
<PkiSyncConnectionField
onChange={() => {
setValue("destinationConfig.dataBagName", "");
}}
/>
<Controller
name="destinationConfig.dataBagName"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Data Bag Name"
tooltipText="Enter your Chef data bag name where certificates will be stored. This data bag will be used to store SSL/TLS certificates, private keys, and certificate chains. Data bag names must contain only alphanumeric characters, underscores, and hyphens."
>
<Input {...field} placeholder="ssl_certificates" maxLength={255} />
</FormControl>
)}
/>
</>
);
};
@@ -4,7 +4,6 @@ import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tab } from "@headlessui/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Switch } from "@app/components/v2";
@@ -16,6 +15,7 @@ import { PkiSyncFormSchema, TPkiSyncForm } from "./schemas/pki-sync-schema";
import { PkiSyncCertificatesFields } from "./PkiSyncCertificatesFields";
import { PkiSyncDestinationFields } from "./PkiSyncDestinationFields";
import { PkiSyncDetailsFields } from "./PkiSyncDetailsFields";
import { PkiSyncFieldMappingsFields } from "./PkiSyncFieldMappingsFields";
import { PkiSyncOptionsFields } from "./PkiSyncOptionsFields";
import { PkiSyncReviewFields } from "./PkiSyncReviewFields";
@@ -26,13 +26,38 @@ type Props = {
initialData?: any;
};
const FORM_TABS: { name: string; key: string; fields: (keyof TPkiSyncForm)[] }[] = [
{ name: "Destination", key: "destination", fields: ["connection", "destinationConfig"] },
{ name: "Sync Options", key: "options", fields: ["syncOptions"] },
{ name: "Details", key: "details", fields: ["name", "description"] },
{ name: "Certificates", key: "certificates", fields: ["certificateIds"] },
{ name: "Review", key: "review", fields: [] }
];
const getFormTabs = (
destination: PkiSync
): { name: string; key: string; fields: (keyof TPkiSyncForm)[] }[] => {
const baseTabs = [
{
name: "Destination",
key: "destination",
fields: ["connection", "destinationConfig"] as (keyof TPkiSyncForm)[]
},
{ name: "Sync Options", key: "options", fields: ["syncOptions"] as (keyof TPkiSyncForm)[] }
];
if (destination === PkiSync.Chef || destination === PkiSync.AwsSecretsManager) {
baseTabs.push({
name: "Mappings",
key: "mappings",
fields: ["syncOptions"] as (keyof TPkiSyncForm)[]
});
}
baseTabs.push(
{ name: "Details", key: "details", fields: ["name", "description"] as (keyof TPkiSyncForm)[] },
{
name: "Certificates",
key: "certificates",
fields: ["certificateIds"] as (keyof TPkiSyncForm)[]
},
{ name: "Review", key: "review", fields: [] as (keyof TPkiSyncForm)[] }
);
return baseTabs;
};
export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialData }: Props) => {
const createPkiSync = useCreatePkiSync();
@@ -42,6 +67,7 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialDa
const [showConfirmation, setShowConfirmation] = useState(false);
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
const FORM_TABS = getFormTabs(destination);
const { syncOption } = usePkiSyncOption(destination);
@@ -55,7 +81,19 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialDa
canImportCertificates: false,
canRemoveCertificates: false,
preserveArn: true,
certificateNameSchema: syncOption?.defaultCertificateNameSchema
certificateNameSchema: syncOption?.defaultCertificateNameSchema,
...((destination === PkiSync.Chef || destination === PkiSync.AwsSecretsManager) && {
fieldMappings: {
certificate: "certificate",
privateKey: "private_key",
certificateChain: "certificate_chain",
caCertificate: "ca_certificate"
}
}),
...(destination === PkiSync.AwsSecretsManager && {
preserveSecretOnRenewal: true,
updateExistingCertificates: true
})
},
...initialData
} as Partial<TPkiSyncForm>,
@@ -167,10 +205,10 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialDa
);
return (
<form className={twMerge(isFinalStep && "max-h-[70vh] overflow-y-auto")}>
<form className="flex max-h-[70vh] flex-col overflow-hidden">
<FormProvider {...formMethods}>
<Tab.Group selectedIndex={selectedTabIndex} onChange={setSelectedTabIndex}>
<Tab.List className="-pb-1 mb-6 w-full border-b-2 border-mineshaft-600">
<Tab.List className="-pb-1 mb-6 w-full flex-shrink-0 border-b-2 border-mineshaft-600">
{FORM_TABS.map((tab, index) => (
<Tab
onClick={async (e) => {
@@ -191,11 +229,11 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialDa
</Tab>
))}
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<Tab.Panels className="flex-1 overflow-y-auto">
<Tab.Panel className="max-h-full overflow-y-auto">
<PkiSyncDestinationFields />
</Tab.Panel>
<Tab.Panel>
<Tab.Panel className="max-h-full overflow-y-auto">
<PkiSyncOptionsFields destination={destination} />
<Controller
control={control}
@@ -225,20 +263,25 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialDa
}}
/>
</Tab.Panel>
<Tab.Panel>
{(destination === PkiSync.Chef || destination === PkiSync.AwsSecretsManager) && (
<Tab.Panel className="max-h-full overflow-y-auto">
<PkiSyncFieldMappingsFields destination={destination} />
</Tab.Panel>
)}
<Tab.Panel className="max-h-full overflow-y-auto">
<PkiSyncDetailsFields />
</Tab.Panel>
<Tab.Panel>
<Tab.Panel className="max-h-full overflow-y-auto">
<PkiSyncCertificatesFields />
</Tab.Panel>
<Tab.Panel>
<Tab.Panel className="max-h-full overflow-y-auto">
<PkiSyncReviewFields />
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
</FormProvider>
<div className="flex w-full flex-row-reverse justify-between gap-4 pt-4">
<div className="mt-4 flex w-full flex-shrink-0 flex-row-reverse justify-between gap-4 border-t border-mineshaft-600 pt-4">
<Button onClick={handleNext} colorSchema="secondary">
{isFinalStep ? "Create Sync" : "Next"}
</Button>
@@ -11,6 +11,7 @@ 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 { PkiSyncFieldMappingsFields } from "./PkiSyncFieldMappingsFields";
import { PkiSyncOptionsFields } from "./PkiSyncOptionsFields";
import { PkiSyncSourceFields } from "./PkiSyncSourceFields";
@@ -66,6 +67,9 @@ export const EditPkiSyncForm = ({ pkiSync, fields, onComplete }: Props) => {
case PkiSyncEditFields.Options:
Component = <PkiSyncOptionsFields destination={pkiSync.destination} />;
break;
case PkiSyncEditFields.Mappings:
Component = <PkiSyncFieldMappingsFields destination={pkiSync.destination} />;
break;
case PkiSyncEditFields.Source:
Component = <PkiSyncSourceFields />;
break;
@@ -4,7 +4,9 @@ import { PkiSync } from "@app/hooks/api/pkiSyncs";
import { TPkiSyncForm } from "./schemas/pki-sync-schema";
import { AwsCertificateManagerPkiSyncFields } from "./AwsCertificateManagerPkiSyncFields";
import { AwsSecretsManagerPkiSyncFields } from "./AwsSecretsManagerPkiSyncFields";
import { AzureKeyVaultPkiSyncFields } from "./AzureKeyVaultPkiSyncFields";
import { ChefPkiSyncFields } from "./ChefPkiSyncFields";
export const PkiSyncDestinationFields = () => {
const { watch } = useFormContext<TPkiSyncForm>();
@@ -16,6 +18,10 @@ export const PkiSyncDestinationFields = () => {
return <AzureKeyVaultPkiSyncFields />;
case PkiSync.AwsCertificateManager:
return <AwsCertificateManagerPkiSyncFields />;
case PkiSync.AwsSecretsManager:
return <AwsSecretsManagerPkiSyncFields />;
case PkiSync.Chef:
return <ChefPkiSyncFields />;
default:
return (
<div className="flex items-center justify-center rounded-md border border-red-500 bg-red-100 p-4 text-red-700">
@@ -0,0 +1,103 @@
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";
type Props = {
destination?: PkiSync;
};
export const PkiSyncFieldMappingsFields = ({ destination }: Props) => {
const { control, watch } = useFormContext<TPkiSyncForm>();
const currentDestination = destination || watch("destination");
if (currentDestination !== PkiSync.Chef && currentDestination !== PkiSync.AwsSecretsManager) {
return null;
}
return (
<>
<p className="mb-4 text-sm text-bunker-300">
Configure how certificate fields are mapped to your{" "}
{currentDestination === PkiSync.Chef ? "Chef data bag items" : "AWS secrets"}.
</p>
<div className="grid grid-cols-2 gap-4">
<Controller
control={control}
name="syncOptions.fieldMappings.certificate"
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Certificate Field"
tooltipText={`The field name used to store the certificate content in the ${currentDestination === PkiSync.Chef ? "Chef data bag item" : "AWS secret"}.`}
>
<Input {...field} placeholder="certificate" />
</FormControl>
)}
/>
<Controller
control={control}
name="syncOptions.fieldMappings.privateKey"
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Private Key Field"
tooltipText={`The field name used to store the private key content in the ${currentDestination === PkiSync.Chef ? "Chef data bag item" : "AWS secret"}.`}
>
<Input {...field} placeholder="private_key" />
</FormControl>
)}
/>
<Controller
control={control}
name="syncOptions.fieldMappings.certificateChain"
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Certificate Chain Field"
tooltipText={`The field name used to store the certificate chain content in the ${currentDestination === PkiSync.Chef ? "Chef data bag item" : "AWS secret"}.`}
>
<Input {...field} placeholder="certificate_chain" />
</FormControl>
)}
/>
<Controller
control={control}
name="syncOptions.fieldMappings.caCertificate"
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="CA Certificate Field"
tooltipText={`The field name used to store the CA certificate content in the ${currentDestination === PkiSync.Chef ? "Chef data bag item" : "AWS secret"}.`}
>
<Input {...field} placeholder="ca_certificate" />
</FormControl>
)}
/>
</div>
<div className="mt-6 rounded-lg border border-mineshaft-600 bg-mineshaft-800 p-4">
<h4 className="mb-2 text-sm font-medium text-mineshaft-100">Preview JSON Structure</h4>
<pre className="text-xs text-bunker-300">
{`{
"id": "certificate-item-name",
"${watch("syncOptions.fieldMappings.certificate") || "certificate"}": "<certificate-content>",
"${watch("syncOptions.fieldMappings.privateKey") || "private_key"}": "<private-key-content>",
"${watch("syncOptions.fieldMappings.certificateChain") || "certificate_chain"}": "<certificate-chain-content>",
"${watch("syncOptions.fieldMappings.caCertificate") || "ca_certificate"}": "<ca-certificate-content>"
}`}
</pre>
</div>
</>
);
};
@@ -95,6 +95,48 @@ export const PkiSyncOptionsFields = ({ destination }: Props) => {
)}
/>
<Controller
control={control}
name="syncOptions.includeRootCa"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Switch
className="bg-mineshaft-400/80 shadow-inner data-[state=checked]:bg-green/80"
id="include-root-ca"
thumbClassName="bg-mineshaft-800"
onCheckedChange={onChange}
isChecked={value}
>
<p>
Include Root CA in Certificate Chain{" "}
<Tooltip
className="max-w-md"
content={
<>
<p>
When enabled, the full certificate chain including the root CA will be
synced to the destination.
</p>
<p className="mt-4">
When disabled, the root CA will be excluded from the certificate chain
during sync operations, reducing the size of the synced certificate chain.
</p>
<p className="mt-4">
Most applications and services work correctly with intermediate certificates
only, as they can validate the trust chain up to a root CA they already
trust.
</p>
</>
}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="ml-1" />
</Tooltip>
</p>
</Switch>
</FormControl>
)}
/>
{currentDestination === PkiSync.AwsCertificateManager && (
<Controller
control={control}
@@ -183,6 +225,97 @@ export const PkiSyncOptionsFields = ({ destination }: Props) => {
/>
)}
{currentDestination === PkiSync.AwsSecretsManager && (
<Controller
control={control}
name="syncOptions.preserveSecretOnRenewal"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Switch
className="bg-mineshaft-400/80 shadow-inner data-[state=checked]:bg-green/80"
id="preserve-secret-on-renewal"
thumbClassName="bg-mineshaft-800"
onCheckedChange={onChange}
isChecked={value}
>
<p>
Preserve Secret on Renewal{" "}
<Tooltip
className="max-w-md"
content={
<>
<p>
<strong>Only applies to certificate renewals:</strong> When a certificate
is renewed in Infisical, this option controls how the renewed certificate
is handled in AWS Secrets Manager.
</p>
<p className="mt-4">
When enabled, the renewed certificate will update the existing secret,
preserving the same secret name and ARN. This allows consuming services to
continue using the same secret reference without requiring updates.
</p>
<p className="mt-4">
When disabled, the renewed certificate will be created as a new secret
with a new name, and the old secret will be removed.
</p>
</>
}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="ml-1" />
</Tooltip>
</p>
</Switch>
</FormControl>
)}
/>
)}
{currentDestination === PkiSync.Chef && (
<Controller
control={control}
name="syncOptions.preserveItemOnRenewal"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Switch
className="bg-mineshaft-400/80 shadow-inner data-[state=checked]:bg-green/80"
id="preserve-item-on-renewal"
thumbClassName="bg-mineshaft-800"
onCheckedChange={onChange}
isChecked={value}
>
<p>
Preserve Data Bag Item on Renewal{" "}
<Tooltip
className="max-w-md"
content={
<>
<p>
<strong>Only applies to certificate renewals:</strong> When a certificate
is renewed in Infisical, this option controls how the renewed certificate
is handled in Chef.
</p>
<p className="mt-4">
When enabled, the renewed certificate will update the existing data bag
item, preserving the same item name. This allows consuming services to
continue using the same data bag item without requiring updates to Chef
cookbooks or recipes.
</p>
<p className="mt-4">
When disabled, the renewed certificate will be created as a new data bag
item with a new name, and the old item will be removed.
</p>
</>
}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="ml-1" />
</Tooltip>
</p>
</Switch>
</FormControl>
)}
/>
)}
<Controller
control={control}
name="syncOptions.certificateNameSchema"
@@ -7,6 +7,7 @@ import { BasePkiSyncSchema } from "./base-pki-sync-schema";
const AwsCertificateManagerSyncOptionsSchema = z.object({
canImportCertificates: z.boolean().default(false),
canRemoveCertificates: z.boolean().default(false),
includeRootCa: z.boolean().default(false),
preserveArn: z.boolean().default(true),
certificateNameSchema: z
.string()
@@ -0,0 +1,98 @@
import { z } from "zod";
import { PkiSync } from "@app/hooks/api/pkiSyncs";
import { BasePkiSyncSchema } from "./base-pki-sync-schema";
const AwsSecretsManagerFieldMappingsSchema = z.object({
certificate: z.string().min(1, "Certificate field name is required").default("certificate"),
privateKey: z.string().min(1, "Private key field name is required").default("private_key"),
certificateChain: z
.string()
.min(1, "Certificate chain field name is required")
.default("certificate_chain"),
caCertificate: z
.string()
.min(1, "CA certificate field name is required")
.default("ca_certificate")
});
const AwsSecretsManagerSyncOptionsSchema = z.object({
canImportCertificates: z.boolean().default(false),
canRemoveCertificates: z.boolean().default(true),
includeRootCa: z.boolean().default(false),
preserveSecretOnRenewal: z.boolean().default(true),
updateExistingCertificates: z.boolean().default(true),
certificateNameSchema: z
.string()
.optional()
.refine(
(val) => {
if (!val) return true;
const allowedOptionalPlaceholders = [
"{{environment}}",
"{{profileId}}",
"{{commonName}}",
"{{friendlyName}}"
];
const allowedPlaceholdersRegexPart = ["{{certificateId}}", ...allowedOptionalPlaceholders]
.map((p) => p.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"))
.join("|");
const allowedContentRegex = new RegExp(
`^([a-zA-Z0-9_\\-]|${allowedPlaceholdersRegexPart})*$`
);
const contentIsValid = allowedContentRegex.test(val);
if (val.trim()) {
const certificateIdRegex = /\{\{certificateId\}\}/;
const certificateIdIsPresent = certificateIdRegex.test(val);
return contentIsValid && certificateIdIsPresent;
}
return contentIsValid;
},
{
message:
"Certificate name schema must include exactly one {{certificateId}} placeholder. It can also include {{environment}}, {{profileId}}, {{commonName}}, or {{friendlyName}} placeholders. Only alphanumeric characters (a-z, A-Z, 0-9), hyphens (-), and underscores (_) are allowed besides the placeholders."
}
),
fieldMappings: AwsSecretsManagerFieldMappingsSchema.optional().default({
certificate: "certificate",
privateKey: "private_key",
certificateChain: "certificate_chain",
caCertificate: "ca_certificate"
})
});
export const AwsSecretsManagerPkiSyncDestinationSchema = BasePkiSyncSchema(
AwsSecretsManagerSyncOptionsSchema
).merge(
z.object({
destination: z.literal(PkiSync.AwsSecretsManager),
destinationConfig: z.object({
region: z.string().min(1, "AWS region is required")
})
})
);
export const UpdateAwsSecretsManagerPkiSyncDestinationSchema =
AwsSecretsManagerPkiSyncDestinationSchema.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.AwsSecretsManager),
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")
})
})
);
@@ -7,6 +7,7 @@ import { BasePkiSyncSchema } from "./base-pki-sync-schema";
const AzureKeyVaultSyncOptionsSchema = z.object({
canImportCertificates: z.boolean().default(false),
canRemoveCertificates: z.boolean().default(true),
includeRootCa: z.boolean().default(false),
enableVersioning: z.boolean().default(true),
certificateNameSchema: z
.string()
@@ -6,6 +6,7 @@ export const BasePkiSyncSchema = <T extends AnyZodObject | undefined = undefined
const baseSyncOptionsSchema = z.object({
canImportCertificates: z.boolean().default(false),
canRemoveCertificates: z.boolean().default(false),
includeRootCa: z.boolean().default(false),
certificateNameSchema: z
.string()
.optional()
@@ -0,0 +1,102 @@
import { z } from "zod";
import { PkiSync } from "@app/hooks/api/pkiSyncs";
import { BasePkiSyncSchema } from "./base-pki-sync-schema";
const ChefFieldMappingsSchema = z.object({
certificate: z.string().min(1, "Certificate field name is required").default("certificate"),
privateKey: z.string().min(1, "Private key field name is required").default("private_key"),
certificateChain: z
.string()
.min(1, "Certificate chain field name is required")
.default("certificate_chain"),
caCertificate: z
.string()
.min(1, "CA certificate field name is required")
.default("ca_certificate")
});
const ChefSyncOptionsSchema = z.object({
canImportCertificates: z.boolean().default(false),
canRemoveCertificates: z.boolean().default(true),
includeRootCa: z.boolean().default(false),
preserveItemOnRenewal: z.boolean().default(true),
updateExistingCertificates: z.boolean().default(true),
certificateNameSchema: z
.string()
.optional()
.refine(
(val) => {
if (!val) return true;
const allowedOptionalPlaceholders = [
"{{environment}}",
"{{profileId}}",
"{{commonName}}",
"{{friendlyName}}"
];
const allowedPlaceholdersRegexPart = ["{{certificateId}}", ...allowedOptionalPlaceholders]
.map((p) => p.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"))
.join("|");
const allowedContentRegex = new RegExp(
`^([a-zA-Z0-9_\\-]|${allowedPlaceholdersRegexPart})*$`
);
const contentIsValid = allowedContentRegex.test(val);
if (val.trim()) {
const certificateIdRegex = /\{\{certificateId\}\}/;
const certificateIdIsPresent = certificateIdRegex.test(val);
return contentIsValid && certificateIdIsPresent;
}
return contentIsValid;
},
{
message:
"Certificate item name schema must include exactly one {{certificateId}} placeholder. It can also include {{environment}}, {{profileId}}, {{commonName}}, or {{friendlyName}} placeholders. Only alphanumeric characters (a-z, A-Z, 0-9), hyphens (-), and underscores (_) are allowed besides the placeholders."
}
),
fieldMappings: ChefFieldMappingsSchema.optional().default({
certificate: "certificate",
privateKey: "private_key",
certificateChain: "certificate_chain",
caCertificate: "ca_certificate"
})
});
export const ChefPkiSyncDestinationSchema = BasePkiSyncSchema(ChefSyncOptionsSchema).merge(
z.object({
destination: z.literal(PkiSync.Chef),
destinationConfig: z.object({
dataBagName: z
.string()
.min(1, "Data bag name is required")
.max(255, "Data bag name must be less than 255 characters")
.regex(
/^[a-zA-Z0-9_-]+$/,
"Data bag name can only contain alphanumeric characters, underscores, and hyphens"
)
})
})
);
export const UpdateChefPkiSyncDestinationSchema = ChefPkiSyncDestinationSchema.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.Chef),
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")
})
})
);
@@ -4,19 +4,31 @@ import {
AwsCertificateManagerPkiSyncDestinationSchema,
UpdateAwsCertificateManagerPkiSyncDestinationSchema
} from "./aws-certificate-manager-pki-sync-destination-schema";
import {
AwsSecretsManagerPkiSyncDestinationSchema,
UpdateAwsSecretsManagerPkiSyncDestinationSchema
} from "./aws-secrets-manager-pki-sync-destination-schema";
import {
AzureKeyVaultPkiSyncDestinationSchema,
UpdateAzureKeyVaultPkiSyncDestinationSchema
} from "./azure-key-vault-pki-sync-destination-schema";
import {
ChefPkiSyncDestinationSchema,
UpdateChefPkiSyncDestinationSchema
} from "./chef-pki-sync-destination-schema";
const PkiSyncUnionSchema = z.discriminatedUnion("destination", [
AzureKeyVaultPkiSyncDestinationSchema,
AwsCertificateManagerPkiSyncDestinationSchema
AwsCertificateManagerPkiSyncDestinationSchema,
AwsSecretsManagerPkiSyncDestinationSchema,
ChefPkiSyncDestinationSchema
]);
const UpdatePkiSyncUnionSchema = z.discriminatedUnion("destination", [
UpdateAzureKeyVaultPkiSyncDestinationSchema,
UpdateAwsCertificateManagerPkiSyncDestinationSchema
UpdateAwsCertificateManagerPkiSyncDestinationSchema,
UpdateAwsSecretsManagerPkiSyncDestinationSchema,
UpdateChefPkiSyncDestinationSchema
]);
export const PkiSyncFormSchema = PkiSyncUnionSchema;
@@ -1,6 +1,7 @@
export enum PkiSyncEditFields {
Details = "details",
Options = "options",
Mappings = "mappings",
Source = "source",
Destination = "destination"
}
@@ -76,8 +76,8 @@ 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-medium text-mineshaft-100">Project Overview</h2>
<div className="justify-betweens mb-8 flex flex-wrap gap-2">
<h2 className="flex-1 text-xl font-medium text-mineshaft-100">Project Overview</h2>
<div className="space-x-2">
<Button
variant="outline_bg"
@@ -157,7 +157,7 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => {
onOpenChange(false);
navigate({
to: getProjectHomePage(project.type, project.environments),
params: { projectId: project.id }
params: { projectId: project.id, orgId: currentOrg.id }
});
};
const onSubmit = handleSubmit((data) => {
@@ -3,14 +3,7 @@ import { ReactNode } from "@tanstack/react-router";
import { LucideIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import {
Badge,
InstanceIcon,
OrgIcon,
ProjectIcon,
SubOrgIcon,
TBadgeProps
} from "@app/components/v3";
import { InstanceIcon, OrgIcon, ProjectIcon, SubOrgIcon } from "@app/components/v3";
import { ProjectType } from "@app/hooks/api/projects/types";
type Props = {
@@ -21,41 +14,40 @@ type Props = {
scope: "org" | "namespace" | "instance" | ProjectType | null;
};
const SCOPE_NAME: Record<NonNullable<Props["scope"]>, { label: string; icon: LucideIcon }> = {
org: { label: "Organization", icon: OrgIcon },
[ProjectType.SecretManager]: { label: "Project", icon: ProjectIcon },
[ProjectType.CertificateManager]: { label: "Project", icon: ProjectIcon },
[ProjectType.SSH]: { label: "Project", icon: ProjectIcon },
[ProjectType.KMS]: { label: "Project", icon: ProjectIcon },
[ProjectType.PAM]: { label: "Project", icon: ProjectIcon },
[ProjectType.SecretScanning]: { label: "Project", icon: ProjectIcon },
namespace: { label: "Sub-Organization", icon: SubOrgIcon },
instance: { label: "Server", icon: InstanceIcon }
};
const SCOPE_VARIANT: Record<NonNullable<Props["scope"]>, TBadgeProps["variant"]> = {
org: "org",
[ProjectType.SecretManager]: "project",
[ProjectType.CertificateManager]: "project",
[ProjectType.SSH]: "project",
[ProjectType.KMS]: "project",
[ProjectType.PAM]: "project",
[ProjectType.SecretScanning]: "project",
namespace: "sub-org",
instance: "neutral"
const SCOPE_BADGE: Record<NonNullable<Props["scope"]>, { icon: LucideIcon; className: string }> = {
org: { className: "text-org", icon: OrgIcon },
[ProjectType.SecretManager]: { className: "text-project", icon: ProjectIcon },
[ProjectType.CertificateManager]: { className: "text-project", icon: ProjectIcon },
[ProjectType.SSH]: { className: "text-project", icon: ProjectIcon },
[ProjectType.KMS]: { className: "text-project", icon: ProjectIcon },
[ProjectType.PAM]: { className: "text-project", icon: ProjectIcon },
[ProjectType.SecretScanning]: { className: "text-project", icon: ProjectIcon },
namespace: { className: "text-sub-org", icon: SubOrgIcon },
instance: { className: "text-neutral", icon: InstanceIcon }
};
export const PageHeader = ({ title, description, children, className, scope }: Props) => (
<div className={twMerge("mb-10 w-full", className)}>
<div className="flex w-full justify-between">
<div className="mr-4 flex w-full items-center">
<h1 className="text-3xl font-medium text-white capitalize">{title}</h1>
{scope && (
<Badge variant={SCOPE_VARIANT[scope]} className="mt-1 ml-2.5">
{createElement(SCOPE_NAME[scope].icon)}
{SCOPE_NAME[scope].label}
</Badge>
)}
<h1
className={twMerge(
"text-3xl font-medium text-white capitalize underline decoration-2 underline-offset-4",
scope === "org" && "decoration-org/90",
scope === "instance" && "decoration-neutral/90",
scope === "namespace" && "decoration-sub-org/90",
Object.values(ProjectType).includes((scope as ProjectType) ?? "") &&
"decoration-project/90",
!scope && "no-underline"
)}
>
{scope &&
createElement(SCOPE_BADGE[scope].icon, {
size: 26,
className: twMerge(SCOPE_BADGE[scope].className, "mr-3 mb-1 inline-block")
})}
{title}
</h1>
</div>
<div className="flex items-center gap-2">{children}</div>
</div>
+2 -2
View File
@@ -47,8 +47,8 @@ export const Tab = ({
}) => (
<TabsPrimitive.Trigger
className={twMerge(
"flex h-10 cursor-pointer items-center justify-center border-transparent",
"px-3 text-sm font-medium whitespace-nowrap text-mineshaft-400 transition-all select-none",
"flex h-11 cursor-pointer items-center justify-center border-transparent",
"px-3 text-sm font-medium whitespace-nowrap text-mineshaft-300/75 transition-all select-none",
"data-[orientation=vertical]:xl:h-5 data-[orientation=vertical]:xl:border-b-0 data-[orientation=vertical]:xl:border-l",
"border-b hover:text-mineshaft-200",
"data-[state=active]:border-mineshaft-400 data-[state=active]:text-white",
@@ -1,4 +1,4 @@
import { BookOpenIcon } from "lucide-react";
import { ExternalLinkIcon } from "lucide-react";
import { Badge } from "@app/components/v3";
@@ -8,10 +8,10 @@ type TDocumentationLinkBadgeProps = {
export function DocumentationLinkBadge({ href }: TDocumentationLinkBadgeProps) {
return (
<Badge variant="neutral" asChild>
<Badge variant="info" asChild>
<a href={href} target="_blank" rel="noopener noreferrer">
<BookOpenIcon />
Documentation
<ExternalLinkIcon />
</a>
</Badge>
);
+162 -162
View File
@@ -25,343 +25,343 @@ export const ROUTE_PATHS = Object.freeze({
Organization: {
Settings: {
OauthCallbackPage: setRoute(
"/organization/settings/oauth/callback",
"/_authenticate/_inject-org-details/_org-layout/organization/settings/oauth/callback"
"/organizations/$orgId/settings/oauth/callback",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/settings/oauth/callback"
)
},
SecretSharing: setRoute(
"/organization/secret-sharing",
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/"
"/organizations/$orgId/secret-sharing",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/secret-sharing/"
),
SettingsPage: setRoute(
"/organization/settings",
"/_authenticate/_inject-org-details/_org-layout/organization/settings/"
"/organizations/$orgId/settings",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/settings/"
),
GroupDetailsByIDPage: setRoute(
"/organization/groups/$groupId",
"/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId"
"/organizations/$orgId/groups/$groupId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/groups/$groupId"
),
IdentityDetailsByIDPage: setRoute(
"/organization/identities/$identityId",
"/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId"
"/organizations/$orgId/identities/$identityId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/identities/$identityId"
),
UserDetailsByIDPage: setRoute(
"/organization/members/$membershipId",
"/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId"
"/organizations/$orgId/members/$membershipId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/members/$membershipId"
),
AccessControlPage: setRoute(
"/organization/access-management",
"/_authenticate/_inject-org-details/_org-layout/organization/access-management"
"/organizations/$orgId/access-management",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/access-management"
),
RoleByIDPage: setRoute(
"/organization/roles/$roleId",
"/_authenticate/_inject-org-details/_org-layout/organization/roles/$roleId"
"/organizations/$orgId/roles/$roleId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/roles/$roleId"
),
AppConnections: {
OauthCallbackPage: setRoute(
"/organization/app-connections/$appConnection/oauth/callback",
"/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback"
"/organizations/$orgId/app-connections/$appConnection/oauth/callback",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/app-connections/$appConnection/oauth/callback"
)
},
NetworkingPage: setRoute(
"/organization/networking",
"/_authenticate/_inject-org-details/_org-layout/organization/networking"
"/organizations/$orgId/networking",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/networking"
)
},
SecretManager: {
ApprovalPage: setRoute(
"/projects/secret-management/$projectId/approval",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/approval"
"/organizations/$orgId/projects/secret-management/$projectId/approval",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/approval"
),
SecretDashboardPage: setRoute(
"/projects/secret-management/$projectId/secrets/$envSlug",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/secrets/$envSlug"
"/organizations/$orgId/projects/secret-management/$projectId/secrets/$envSlug",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/secrets/$envSlug"
),
RollbackPreviewPage: setRoute(
"/projects/secret-management/$projectId/commits/$environment/$folderId/$commitId/restore",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/commits/$environment/$folderId/$commitId/restore"
"/organizations/$orgId/projects/secret-management/$projectId/commits/$environment/$folderId/$commitId/restore",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/commits/$environment/$folderId/$commitId/restore"
),
CommitDetailsPage: setRoute(
"/projects/secret-management/$projectId/commits/$environment/$folderId/$commitId",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/commits/$environment/$folderId/$commitId"
"/organizations/$orgId/projects/secret-management/$projectId/commits/$environment/$folderId/$commitId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/commits/$environment/$folderId/$commitId"
),
CommitsPage: setRoute(
"/projects/secret-management/$projectId/commits/$environment/$folderId",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/commits/$environment/$folderId"
"/organizations/$orgId/projects/secret-management/$projectId/commits/$environment/$folderId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/commits/$environment/$folderId"
),
OverviewPage: setRoute(
"/projects/secret-management/$projectId/overview",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/overview"
"/organizations/$orgId/projects/secret-management/$projectId/overview",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/overview"
),
IntegrationsListPage: setRoute(
"/projects/secret-management/$projectId/integrations",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/"
"/organizations/$orgId/projects/secret-management/$projectId/integrations",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/"
),
IntegrationDetailsByIDPage: setRoute(
"/projects/secret-management/$projectId/integrations/$integrationId",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/$integrationId"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/$integrationId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/$integrationId"
),
SecretSyncDetailsByIDPage: setRoute(
"/projects/secret-management/$projectId/integrations/secret-syncs/$destination/$syncId",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/secret-syncs/$destination/$syncId"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/secret-syncs/$destination/$syncId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/secret-syncs/$destination/$syncId"
),
Integratons: {
SelectIntegrationAuth: setRoute(
"/projects/secret-management/$projectId/integrations/select-integration-auth",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/select-integration-auth"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/select-integration-auth",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/select-integration-auth"
),
HerokuOauthCallbackPage: setRoute(
"/projects/secret-management/$projectId/integrations/heroku/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/heroku/oauth2/callback"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/heroku/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/heroku/oauth2/callback"
),
HerokuConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/heroku/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/heroku/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/heroku/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/heroku/create"
),
AwsParameterStoreConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/aws-parameter-store/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/aws-parameter-store/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/aws-parameter-store/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/aws-parameter-store/create"
),
AwsSecretManagerConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/aws-secret-manager/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/aws-secret-manager/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/aws-secret-manager/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/aws-secret-manager/create"
),
AzureAppConfigurationsOauthCallbackPage: setRoute(
"/projects/secret-management/$projectId/integrations/azure-app-configuration/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-app-configuration/oauth2/callback"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/azure-app-configuration/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-app-configuration/oauth2/callback"
),
AzureAppConfigurationsConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/azure-app-configuration/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-app-configuration/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/azure-app-configuration/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-app-configuration/create"
),
AzureDevopsConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/azure-devops/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-devops/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/azure-devops/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-devops/create"
),
AzureKeyVaultAuthorizePage: setRoute(
"/projects/secret-management/$projectId/integrations/azure-key-vault/authorize",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-key-vault/authorize"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/azure-key-vault/authorize",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-key-vault/authorize"
),
AzureKeyVaultOauthCallbackPage: setRoute(
"/projects/secret-management/$projectId/integrations/azure-key-vault/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-key-vault/oauth2/callback"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/azure-key-vault/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-key-vault/oauth2/callback"
),
AzureKeyVaultConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/azure-key-vault/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-key-vault/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/azure-key-vault/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/azure-key-vault/create"
),
BitbucketOauthCallbackPage: setRoute(
"/projects/secret-management/$projectId/integrations/bitbucket/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/bitbucket/oauth2/callback"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/bitbucket/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/bitbucket/oauth2/callback"
),
BitbucketConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/bitbucket/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/bitbucket/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/bitbucket/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/bitbucket/create"
),
ChecklyConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/checkly/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/checkly/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/checkly/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/checkly/create"
),
CircleConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/circleci/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/circleci/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/circleci/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/circleci/create"
),
CloudflarePagesConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/cloudflare-pages/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/cloudflare-pages/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/cloudflare-pages/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/cloudflare-pages/create"
),
DigitalOceanAppPlatformConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/digital-ocean-app-platform/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/digital-ocean-app-platform/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/digital-ocean-app-platform/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/digital-ocean-app-platform/create"
),
CloudflareWorkersConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/cloudflare-workers/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/cloudflare-workers/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/cloudflare-workers/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/cloudflare-workers/create"
),
CodefreshConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/codefresh/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/codefresh/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/codefresh/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/codefresh/create"
),
GcpSecretManagerConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/gcp-secret-manager/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/gcp-secret-manager/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/gcp-secret-manager/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/gcp-secret-manager/create"
),
GcpSecretManagerOauthCallbackPage: setRoute(
"/projects/secret-management/$projectId/integrations/gcp-secret-manager/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/gcp-secret-manager/oauth2/callback"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/gcp-secret-manager/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/gcp-secret-manager/oauth2/callback"
),
GithubConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/github/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/github/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/github/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/github/create"
),
GithubOauthCallbackPage: setRoute(
"/projects/secret-management/$projectId/integrations/github/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/github/oauth2/callback"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/github/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/github/oauth2/callback"
),
GitlabConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/gitlab/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/gitlab/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/gitlab/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/gitlab/create"
),
GitlabOauthCallbackPage: setRoute(
"/projects/secret-management/$projectId/integrations/gitlab/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/gitlab/oauth2/callback"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/gitlab/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/gitlab/oauth2/callback"
),
VercelOauthCallbackPage: setRoute(
"/projects/secret-management/$projectId/integrations/vercel/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/vercel/oauth2/callback"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/vercel/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/vercel/oauth2/callback"
),
VercelConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/vercel/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/vercel/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/vercel/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/vercel/create"
),
FlyioConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/flyio/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/flyio/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/flyio/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/flyio/create"
),
HashicorpVaultConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/hashicorp-vault/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/hashicorp-vault/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/hashicorp-vault/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/hashicorp-vault/create"
),
HasuraCloudConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/hasura-cloud/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/hasura-cloud/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/hasura-cloud/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/hasura-cloud/create"
),
LaravelForgeConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/laravel-forge/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/laravel-forge/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/laravel-forge/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/laravel-forge/create"
),
NorthflankConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/northflank/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/northflank/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/northflank/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/northflank/create"
),
RailwayConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/railway/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/railway/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/railway/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/railway/create"
),
RenderConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/render/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/render/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/render/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/render/create"
),
RundeckConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/rundeck/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/rundeck/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/rundeck/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/rundeck/create"
),
WindmillConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/windmill/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/windmill/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/windmill/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/windmill/create"
),
TravisCIConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/travisci/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/travisci/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/travisci/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/travisci/create"
),
TerraformCloudConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/terraform-cloud/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/terraform-cloud/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/terraform-cloud/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/terraform-cloud/create"
),
TeamcityConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/teamcity/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/teamcity/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/teamcity/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/teamcity/create"
),
SupabaseConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/supabase/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/supabase/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/supabase/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/supabase/create"
),
OctopusDeployCloudConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/octopus-deploy/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/octopus-deploy/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/octopus-deploy/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/octopus-deploy/create"
),
DatabricksConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/databricks/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/databricks/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/databricks/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/databricks/create"
),
QoveryConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/qovery/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/qovery/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/qovery/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/qovery/create"
),
Cloud66ConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/cloud-66/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/cloud-66/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/cloud-66/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/cloud-66/create"
),
NetlifyConfigurePage: setRoute(
"/projects/secret-management/$projectId/integrations/netlify/create",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/netlify/create"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/netlify/create",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/netlify/create"
),
NetlifyOuathCallbackPage: setRoute(
"/projects/secret-management/$projectId/integrations/netlify/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/netlify/oauth2/callback"
"/organizations/$orgId/projects/secret-management/$projectId/integrations/netlify/oauth2/callback",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/integrations/netlify/oauth2/callback"
)
}
},
CertManager: {
CertAuthDetailsByIDPage: setRoute(
"/projects/cert-management/$projectId/ca/$caName",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/ca/$caName"
"/organizations/$orgId/projects/cert-management/$projectId/ca/$caId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/cert-management/$projectId/_cert-manager-layout/ca/$caId"
),
SubscribersPage: setRoute(
"/projects/cert-management/$projectId/subscribers",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers"
"/organizations/$orgId/projects/cert-management/$projectId/subscribers",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/cert-management/$projectId/_cert-manager-layout/subscribers"
),
CertificateAuthoritiesPage: setRoute(
"/projects/cert-management/$projectId/certificate-authorities",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-authorities"
"/organizations/$orgId/projects/cert-management/$projectId/certificate-authorities",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/cert-management/$projectId/_cert-manager-layout/certificate-authorities"
),
AlertingPage: setRoute(
"/projects/cert-management/$projectId/alerting",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/alerting"
"/organizations/$orgId/projects/cert-management/$projectId/alerting",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/cert-management/$projectId/_cert-manager-layout/alerting"
),
PkiCollectionDetailsByIDPage: setRoute(
"/projects/cert-management/$projectId/pki-collections/$collectionId",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/pki-collections/$collectionId"
"/organizations/$orgId/projects/cert-management/$projectId/pki-collections/$collectionId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/cert-management/$projectId/_cert-manager-layout/pki-collections/$collectionId"
),
PkiSubscriberDetailsByIDPage: setRoute(
"/projects/cert-management/$projectId/subscribers/$subscriberName",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers/$subscriberName"
"/organizations/$orgId/projects/cert-management/$projectId/subscribers/$subscriberName",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/cert-management/$projectId/_cert-manager-layout/subscribers/$subscriberName"
),
IntegrationsListPage: setRoute(
"/projects/cert-management/$projectId/integrations",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/integrations/"
"/organizations/$orgId/projects/cert-management/$projectId/integrations",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/cert-management/$projectId/_cert-manager-layout/integrations/"
),
PkiSyncDetailsByIDPage: setRoute(
"/projects/cert-management/$projectId/integrations/$syncId",
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/integrations/$syncId"
"/organizations/$orgId/projects/cert-management/$projectId/integrations/$syncId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/cert-management/$projectId/_cert-manager-layout/integrations/$syncId"
)
},
Ssh: {
SshCaByIDPage: setRoute(
"/projects/ssh/$projectId/ca/$caId",
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/ca/$caId"
"/organizations/$orgId/projects/ssh/$projectId/ca/$caId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/ssh/$projectId/_ssh-layout/ca/$caId"
),
SshHostGroupDetailsByIDPage: setRoute(
"/projects/ssh/$projectId/ssh-host-groups/$sshHostGroupId",
"/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/ssh-host-groups/$sshHostGroupId"
"/organizations/$orgId/projects/ssh/$projectId/ssh-host-groups/$sshHostGroupId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/ssh/$projectId/_ssh-layout/ssh-host-groups/$sshHostGroupId"
)
},
SecretScanning: {
DataSourceByIdPage: setRoute(
"/projects/secret-scanning/$projectId/data-sources/$type/$dataSourceId",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources/$type/$dataSourceId"
"/organizations/$orgId/projects/secret-scanning/$projectId/data-sources/$type/$dataSourceId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources/$type/$dataSourceId"
),
FindingsPage: setRoute(
"/projects/secret-scanning/$projectId/findings",
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/findings"
"/organizations/$orgId/projects/secret-scanning/$projectId/findings",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-scanning/$projectId/_secret-scanning-layout/findings"
)
},
Pam: {
AccountsPage: setRoute(
"/projects/pam/$projectId/accounts",
"/_authenticate/_inject-org-details/_org-layout/projects/pam/$projectId/_pam-layout/accounts"
"/organizations/$orgId/projects/pam/$projectId/accounts",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/pam/$projectId/_pam-layout/accounts"
),
ResourcesPage: setRoute(
"/projects/pam/$projectId/resources",
"/_authenticate/_inject-org-details/_org-layout/projects/pam/$projectId/_pam-layout/resources"
"/organizations/$orgId/projects/pam/$projectId/resources",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/pam/$projectId/_pam-layout/resources"
),
SessionsPage: setRoute(
"/projects/pam/$projectId/sessions",
"/_authenticate/_inject-org-details/_org-layout/projects/pam/$projectId/_pam-layout/sessions/"
"/organizations/$orgId/projects/pam/$projectId/sessions",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/pam/$projectId/_pam-layout/sessions/"
),
PamSessionByIDPage: setRoute(
"/projects/pam/$projectId/sessions/$sessionId",
"/_authenticate/_inject-org-details/_org-layout/projects/pam/$projectId/_pam-layout/sessions/$sessionId"
"/organizations/$orgId/projects/pam/$projectId/sessions/$sessionId",
"/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/pam/$projectId/_pam-layout/sessions/$sessionId"
)
},
Public: {
@@ -4,6 +4,7 @@ export {
ProjectPermissionActions,
ProjectPermissionAuditLogsActions,
ProjectPermissionCertificateActions,
ProjectPermissionCertificateAuthorityActions,
ProjectPermissionCertificateProfileActions,
ProjectPermissionCmekActions,
ProjectPermissionDynamicSecretActions,
@@ -12,6 +13,7 @@ export {
ProjectPermissionKmipActions,
ProjectPermissionMemberActions,
ProjectPermissionPkiSubscriberActions,
ProjectPermissionPkiSyncActions,
ProjectPermissionPkiTemplateActions,
ProjectPermissionSshHostActions,
ProjectPermissionSub
@@ -12,7 +12,9 @@ export enum ProjectPermissionCertificateActions {
Create = "create",
Edit = "edit",
Delete = "delete",
ReadPrivateKey = "read-private-key"
List = "list",
ReadPrivateKey = "read-private-key",
Import = "import"
}
export enum ProjectPermissionSecretActions {
@@ -67,6 +69,7 @@ export enum ProjectPermissionPkiSyncActions {
Create = "create",
Edit = "edit",
Delete = "delete",
List = "list",
SyncCertificates = "sync-certificates",
ImportCertificates = "import-certificates",
RemoveCertificates = "remove-certificates"
@@ -128,13 +131,25 @@ export enum ProjectPermissionPkiTemplateActions {
ListCerts = "list-certs"
}
export enum ProjectPermissionCertificateProfileActions {
export enum ProjectPermissionCertificateAuthorityActions {
Read = "read",
Create = "create",
Edit = "edit",
Delete = "delete",
List = "list",
Renew = "renew",
SignIntermediate = "sign-intermediate"
}
export enum ProjectPermissionCertificateProfileActions {
Read = "read",
List = "list",
Create = "create",
Edit = "edit",
Delete = "delete",
IssueCert = "issue-cert",
RevealAcmeEabSecret = "reveal-acme-eab-secret"
RevealAcmeEabSecret = "reveal-acme-eab-secret",
RotateAcmeEabSecret = "rotate-acme-eab-secret"
}
export enum ProjectPermissionSecretRotationActions {
@@ -230,6 +245,9 @@ export type ConditionalProjectPermissionSubject =
| ProjectPermissionSub.SshHosts
| ProjectPermissionSub.PkiSubscribers
| ProjectPermissionSub.CertificateTemplates
| ProjectPermissionSub.CertificateAuthorities
| ProjectPermissionSub.Certificates
| ProjectPermissionSub.CertificateProfiles
| ProjectPermissionSub.SecretFolders
| ProjectPermissionSub.SecretImports
| ProjectPermissionSub.SecretRotation
@@ -361,7 +379,22 @@ export type SecretSyncSubjectFields = {
};
export type PkiSyncSubjectFields = {
subscriberId: string;
subscriberName: string;
name: string;
};
export type CertificateAuthoritySubjectFields = {
name: string;
};
export type CertificateSubjectFields = {
commonName?: string;
altNames?: string;
serialNumber?: string;
};
export type CertificateProfileSubjectFields = {
slug: string;
};
export type SecretRotationSubjectFields = {
@@ -457,8 +490,21 @@ export type ProjectPermissionSet =
| (ForcedSubject<ProjectPermissionSub.Identity> & IdentityManagementSubjectFields)
)
]
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
| [ProjectPermissionCertificateActions, ProjectPermissionSub.Certificates]
| [
ProjectPermissionCertificateAuthorityActions,
(
| ProjectPermissionSub.CertificateAuthorities
| (ForcedSubject<ProjectPermissionSub.CertificateAuthorities> &
CertificateAuthoritySubjectFields)
)
]
| [
ProjectPermissionCertificateActions,
(
| ProjectPermissionSub.Certificates
| (ForcedSubject<ProjectPermissionSub.Certificates> & CertificateSubjectFields)
)
]
| [
ProjectPermissionPkiTemplateActions,
(
@@ -484,7 +530,14 @@ export type ProjectPermissionSet =
| (ForcedSubject<ProjectPermissionSub.PkiSubscribers> & PkiSubscriberSubjectFields)
)
]
| [ProjectPermissionCertificateProfileActions, ProjectPermissionSub.CertificateProfiles]
| [
ProjectPermissionCertificateProfileActions,
(
| ProjectPermissionSub.CertificateProfiles
| (ForcedSubject<ProjectPermissionSub.CertificateProfiles> &
CertificateProfileSubjectFields)
)
]
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
+2
View File
@@ -15,6 +15,7 @@ export {
ProjectPermissionActions,
ProjectPermissionAuditLogsActions,
ProjectPermissionCertificateActions,
ProjectPermissionCertificateAuthorityActions,
ProjectPermissionCertificateProfileActions,
ProjectPermissionCmekActions,
ProjectPermissionDynamicSecretActions,
@@ -23,6 +24,7 @@ export {
ProjectPermissionKmipActions,
ProjectPermissionMemberActions,
ProjectPermissionPkiSubscriberActions,
ProjectPermissionPkiSyncActions,
ProjectPermissionPkiTemplateActions,
ProjectPermissionSshHostActions,
ProjectPermissionSub,
+4
View File
@@ -49,6 +49,7 @@ import { BitbucketConnectionMethod } from "@app/hooks/api/appConnections/types/b
import { ChecklyConnectionMethod } from "@app/hooks/api/appConnections/types/checkly-connection";
import { ChefConnectionMethod } from "@app/hooks/api/appConnections/types/chef-connection";
import { DigitalOceanConnectionMethod } from "@app/hooks/api/appConnections/types/digital-ocean";
import { DNSMadeEasyConnectionMethod } from "@app/hooks/api/appConnections/types/dns-made-easy-connection";
import { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/heroku-connection";
import { LaravelForgeConnectionMethod } from "@app/hooks/api/appConnections/types/laravel-forge-connection";
import { NetlifyConnectionMethod } from "@app/hooks/api/appConnections/types/netlify-connection";
@@ -111,6 +112,7 @@ export const APP_CONNECTION_MAP: Record<
[AppConnection.Flyio]: { name: "Fly.io", image: "Flyio.svg" },
[AppConnection.GitLab]: { name: "GitLab", image: "GitLab.png" },
[AppConnection.Cloudflare]: { name: "Cloudflare", image: "Cloudflare.png" },
[AppConnection.DNSMadeEasy]: { name: "DNS Made Easy", image: "DNSMadeEasy.svg", size: 120 },
[AppConnection.Zabbix]: { name: "Zabbix", image: "Zabbix.png" },
[AppConnection.Railway]: { name: "Railway", image: "Railway.png" },
[AppConnection.Bitbucket]: { name: "Bitbucket", image: "Bitbucket.png" },
@@ -214,6 +216,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
return { name: "Client Secret", icon: faKey };
case AzureClientSecretsConnectionMethod.Certificate:
return { name: "Certificate", icon: faCertificate };
case DNSMadeEasyConnectionMethod.APIKeySecret:
return { name: "API Key & Secret", icon: faKey };
default:
throw new Error(`Unhandled App Connection Method: ${method}`);
}
+11 -1
View File
@@ -15,10 +15,20 @@ export const PKI_SYNC_MAP: Record<
[PkiSync.AwsCertificateManager]: {
name: "AWS Certificate Manager",
image: "Amazon Web Services.png"
},
[PkiSync.AwsSecretsManager]: {
name: "AWS Secrets Manager",
image: "Amazon Web Services.png"
},
[PkiSync.Chef]: {
name: "Chef",
image: "Chef.png"
}
};
export const PKI_SYNC_CONNECTION_MAP: Record<PkiSync, AppConnection> = {
[PkiSync.AzureKeyVault]: AppConnection.AzureKeyVault,
[PkiSync.AwsCertificateManager]: AppConnection.AWS
[PkiSync.AwsCertificateManager]: AppConnection.AWS,
[PkiSync.AwsSecretsManager]: AppConnection.AWS,
[PkiSync.Chef]: AppConnection.Chef
};
+8 -13
View File
@@ -14,11 +14,6 @@ const secretsToBeAdded = [
secretValue: "OVERRIDE_THIS",
secretComment: "Override secrets with personal value"
},
{
secretKey: "DB_PASSWORD",
secretValue: "OVERRIDE_THIS",
secretComment: "Another secret override"
},
{
secretKey: "DB_PASSWORD",
secretValue: "example_password"
@@ -64,11 +59,11 @@ export const initProjectHelper = async ({ projectName }: { projectName: string }
export const getProjectBaseURL = (type: ProjectType) => {
switch (type) {
case ProjectType.SecretManager:
return "/projects/secret-management/$projectId";
return "/organizations/$orgId/projects/secret-management/$projectId";
case ProjectType.CertificateManager:
return "/projects/cert-management/$projectId";
return "/organizations/$orgId/projects/cert-management/$projectId";
default:
return `/projects/${type}/$projectId` as const;
return `/organizations/$orgId/projects/${type}/$projectId` as const;
}
};
@@ -77,15 +72,15 @@ export const getProjectBaseURL = (type: ProjectType) => {
export const getProjectHomePage = (type: ProjectType, environments: ProjectEnv[]) => {
switch (type) {
case ProjectType.SecretManager:
return "/projects/secret-management/$projectId/overview" as const;
return "/organizations/$orgId/projects/secret-management/$projectId/overview" as const;
case ProjectType.CertificateManager:
return "/projects/cert-management/$projectId/policies" as const;
return "/organizations/$orgId/projects/cert-management/$projectId/policies" as const;
case ProjectType.SecretScanning:
return `/projects/${type}/$projectId/data-sources` as const;
return `/organizations/$orgId/projects/${type}/$projectId/data-sources` as const;
case ProjectType.PAM:
return `/projects/${type}/$projectId/accounts` as const;
return `/organizations/$orgId/projects/${type}/$projectId/accounts` as const;
default:
return `/projects/${type}/$projectId/overview` as const;
return `/organizations/$orgId/projects/${type}/$projectId/overview` as const;
}
};
@@ -0,0 +1,2 @@
export * from "./queries";
export * from "./types";
@@ -0,0 +1,37 @@
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { appConnectionKeys } from "../queries";
import { TDNSMadeEasyZone } from "./types";
const dnsMadeEasyConnectionKeys = {
all: [...appConnectionKeys.all, "dns-made-easy"] as const,
listZones: (connectionId: string) =>
[...dnsMadeEasyConnectionKeys.all, "zones", connectionId] as const
};
export const useDNSMadeEasyConnectionListZones = (
connectionId: string,
options?: Omit<
UseQueryOptions<
TDNSMadeEasyZone[],
unknown,
TDNSMadeEasyZone[],
ReturnType<typeof dnsMadeEasyConnectionKeys.listZones>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: dnsMadeEasyConnectionKeys.listZones(connectionId),
queryFn: async () => {
const { data } = await apiRequest.get<TDNSMadeEasyZone[]>(
`/api/v1/app-connections/dns-made-easy/${connectionId}/dns-made-easy-zones`
);
return data;
},
...options
});
};
@@ -0,0 +1,4 @@
export type TDNSMadeEasyZone = {
id: string;
name: string;
};
@@ -29,6 +29,7 @@ export enum AppConnection {
Flyio = "flyio",
GitLab = "gitlab",
Cloudflare = "cloudflare",
DNSMadeEasy = "dns-made-easy",
Bitbucket = "bitbucket",
Zabbix = "zabbix",
Railway = "railway",
@@ -184,6 +184,10 @@ export type TRedisConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.Redis;
};
export type TDNSMadeEasyConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.DNSMadeEasy;
};
export type TAppConnectionOption =
| TAwsConnectionOption
| TGitHubConnectionOption
@@ -225,7 +229,8 @@ export type TAppConnectionOption =
| TOktaConnectionOption
| TAzureAdCsConnectionOption
| TLaravelForgeConnectionOption
| TChefConnectionOption;
| TChefConnectionOption
| TDNSMadeEasyConnectionOption;
export type TAppConnectionOptionMap = {
[AppConnection.AWS]: TAwsConnectionOption;
@@ -257,6 +262,7 @@ export type TAppConnectionOptionMap = {
[AppConnection.Flyio]: TFlyioConnectionOption;
[AppConnection.GitLab]: TGitlabConnectionOption;
[AppConnection.Cloudflare]: TCloudflareConnectionOption;
[AppConnection.DNSMadeEasy]: TDNSMadeEasyConnectionOption;
[AppConnection.Bitbucket]: TBitbucketConnectionOption;
[AppConnection.Zabbix]: TZabbixConnectionOption;
[AppConnection.Railway]: TRailwayConnectionOption;
@@ -0,0 +1,14 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
export enum DNSMadeEasyConnectionMethod {
APIKeySecret = "api-key-secret"
}
export type TDNSMadeEasyConnection = TRootAppConnection & { app: AppConnection.DNSMadeEasy } & {
method: DNSMadeEasyConnectionMethod.APIKeySecret;
credentials: {
apiKey: string;
secretKey: string;
};
};
@@ -15,6 +15,7 @@ import { TChefConnection } from "./chef-connection";
import { TCloudflareConnection } from "./cloudflare-connection";
import { TDatabricksConnection } from "./databricks-connection";
import { TDigitalOceanConnection } from "./digital-ocean";
import { TDNSMadeEasyConnection } from "./dns-made-easy-connection";
import { TFlyioConnection } from "./flyio-connection";
import { TGcpConnection } from "./gcp-connection";
import { TGitHubConnection } from "./github-connection";
@@ -57,6 +58,7 @@ export * from "./checkly-connection";
export * from "./chef-connection";
export * from "./cloudflare-connection";
export * from "./databricks-connection";
export * from "./dns-made-easy-connection";
export * from "./flyio-connection";
export * from "./gcp-connection";
export * from "./github-connection";
@@ -127,7 +129,8 @@ export type TAppConnection =
| TNorthflankConnection
| TOktaConnection
| TRedisConnection
| TChefConnection;
| TChefConnection
| TDNSMadeEasyConnection;
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id" | "projectId">;
+23 -21
View File
@@ -72,7 +72,7 @@ export const eventToNameMap: { [K in EventType]: string } = {
[EventType.SIGN_INTERMEDIATE]: "Sign intermediate",
[EventType.IMPORT_CA_CERT]: "Import CA certificate",
[EventType.GET_CA_CRL]: "Get CA CRL",
[EventType.ISSUE_CERT]: "Issue certificate",
[EventType.ISSUE_CERT]: "Request certificate",
[EventType.IMPORT_CERT]: "Import certificate",
[EventType.GET_CERT]: "Get certificate",
[EventType.DELETE_CERT]: "Delete certificate",
@@ -225,7 +225,7 @@ export const eventToNameMap: { [K in EventType]: string } = {
[EventType.UPDATE_PKI_SUBSCRIBER]: "Update PKI subscriber",
[EventType.DELETE_PKI_SUBSCRIBER]: "Delete PKI subscriber",
[EventType.GET_PKI_SUBSCRIBER]: "Get PKI subscriber",
[EventType.ISSUE_PKI_SUBSCRIBER_CERT]: "Issue PKI subscriber certificate",
[EventType.ISSUE_PKI_SUBSCRIBER_CERT]: "Request PKI subscriber certificate",
[EventType.SIGN_PKI_SUBSCRIBER_CERT]: "Sign PKI subscriber certificate",
[EventType.AUTOMATED_RENEW_SUBSCRIBER_CERT]: "Automated renew PKI subscriber certificate",
[EventType.LIST_PKI_SUBSCRIBER_CERTS]: "List PKI subscriber certificates",
@@ -262,31 +262,32 @@ export const eventToNameMap: { [K in EventType]: string } = {
[EventType.UPDATE_IDENTITY_PROJECT_MEMBERSHIP]: "Update Identity Project Membership",
[EventType.DELETE_IDENTITY_PROJECT_MEMBERSHIP]: "Delete Identity Project Membership",
[EventType.PAM_SESSION_START]: "PAM Session Start",
[EventType.PAM_SESSION_LOGS_UPDATE]: "PAM Session Logs Update",
[EventType.PAM_SESSION_END]: "PAM Session End",
[EventType.PAM_SESSION_GET]: "PAM Session Get",
[EventType.PAM_SESSION_LIST]: "PAM Session List",
[EventType.PAM_FOLDER_CREATE]: "PAM Folder Create",
[EventType.PAM_FOLDER_UPDATE]: "PAM Folder Update",
[EventType.PAM_FOLDER_DELETE]: "PAM Folder Delete",
[EventType.PAM_ACCOUNT_LIST]: "PAM Account List",
[EventType.PAM_ACCOUNT_ACCESS]: "PAM Account Access",
[EventType.PAM_ACCOUNT_CREATE]: "PAM Account Create",
[EventType.PAM_ACCOUNT_UPDATE]: "PAM Account Update",
[EventType.PAM_ACCOUNT_DELETE]: "PAM Account Delete",
[EventType.PAM_RESOURCE_LIST]: "PAM Resource List",
[EventType.PAM_RESOURCE_GET]: "PAM Resource Get",
[EventType.PAM_RESOURCE_CREATE]: "PAM Resource Create",
[EventType.PAM_RESOURCE_UPDATE]: "PAM Resource Update",
[EventType.PAM_RESOURCE_DELETE]: "PAM Resource Delete",
[EventType.PAM_SESSION_CREDENTIALS_GET]: "Get PAM Session Credentials",
[EventType.PAM_SESSION_START]: "Start PAM Session",
[EventType.PAM_SESSION_LOGS_UPDATE]: "Update PAM Session Logs",
[EventType.PAM_SESSION_END]: "End PAM Session",
[EventType.PAM_SESSION_GET]: "Get PAM Session",
[EventType.PAM_SESSION_LIST]: "List PAM Sessions",
[EventType.PAM_FOLDER_CREATE]: "Create PAM Folder",
[EventType.PAM_FOLDER_UPDATE]: "Update PAM Folder",
[EventType.PAM_FOLDER_DELETE]: "Delete PAM Folder",
[EventType.PAM_ACCOUNT_LIST]: "List PAM Accounts",
[EventType.PAM_ACCOUNT_ACCESS]: "Access PAM Account",
[EventType.PAM_ACCOUNT_CREATE]: "Create PAM Account",
[EventType.PAM_ACCOUNT_UPDATE]: "Update PAM Account",
[EventType.PAM_ACCOUNT_DELETE]: "Delete PAM Account",
[EventType.PAM_RESOURCE_LIST]: "List PAM Resources",
[EventType.PAM_RESOURCE_GET]: "Get PAM Resource",
[EventType.PAM_RESOURCE_CREATE]: "Create PAM Resource",
[EventType.PAM_RESOURCE_UPDATE]: "Update PAM Resource",
[EventType.PAM_RESOURCE_DELETE]: "Delete PAM Resource",
[EventType.CREATE_CERTIFICATE_PROFILE]: "Create Certificate Profile",
[EventType.UPDATE_CERTIFICATE_PROFILE]: "Update Certificate Profile",
[EventType.DELETE_CERTIFICATE_PROFILE]: "Delete Certificate Profile",
[EventType.GET_CERTIFICATE_PROFILE]: "Get Certificate Profile",
[EventType.LIST_CERTIFICATE_PROFILES]: "List Certificate Profiles",
[EventType.ISSUE_CERTIFICATE_FROM_PROFILE]: "Issue Certificate From Profile",
[EventType.ISSUE_CERTIFICATE_FROM_PROFILE]: "Request Certificate From Profile",
[EventType.SIGN_CERTIFICATE_FROM_PROFILE]: "Sign Certificate From Profile",
[EventType.ORDER_CERTIFICATE_FROM_PROFILE]: "Order Certificate From Profile",
[EventType.GET_CERTIFICATE_PROFILE_LATEST_ACTIVE_BUNDLE]:
@@ -314,6 +315,7 @@ const sharedProjectEvents = [
export const projectToEventsMap: Partial<Record<ProjectType, EventType[]>> = {
[ProjectType.PAM]: [
...sharedProjectEvents,
EventType.PAM_SESSION_CREDENTIALS_GET,
EventType.PAM_SESSION_START,
EventType.PAM_SESSION_LOGS_UPDATE,
EventType.PAM_SESSION_END,
@@ -254,6 +254,7 @@ export enum EventType {
UPDATE_IDENTITY_PROJECT_MEMBERSHIP = "update-identity-project-membership",
DELETE_IDENTITY_PROJECT_MEMBERSHIP = "delete-identity-project-membership",
PAM_SESSION_CREDENTIALS_GET = "pam-session-credentials-get",
PAM_SESSION_START = "pam-session-start",
PAM_SESSION_LOGS_UPDATE = "pam-session-logs-update",
PAM_SESSION_END = "pam-session-end",
+4 -2
View File
@@ -16,12 +16,14 @@ export const caStatusToNameMap: { [K in CaStatus]: string } = {
export const ACME_DNS_PROVIDER_NAME_MAP: Record<AcmeDnsProvider, string> = {
[AcmeDnsProvider.ROUTE53]: "Route53",
[AcmeDnsProvider.Cloudflare]: "Cloudflare"
[AcmeDnsProvider.Cloudflare]: "Cloudflare",
[AcmeDnsProvider.DNSMadeEasy]: "DNS Made Easy"
};
export const ACME_DNS_PROVIDER_APP_CONNECTION_MAP: Record<AcmeDnsProvider, AppConnection> = {
[AcmeDnsProvider.ROUTE53]: AppConnection.AWS,
[AcmeDnsProvider.Cloudflare]: AppConnection.Cloudflare
[AcmeDnsProvider.Cloudflare]: AppConnection.Cloudflare,
[AcmeDnsProvider.DNSMadeEasy]: AppConnection.DNSMadeEasy
};
export const CA_TYPE_CAPABILITIES_MAP: Record<CaType, CaCapability[]> = {
+2 -1
View File
@@ -21,7 +21,8 @@ export enum CaRenewalType {
export enum AcmeDnsProvider {
ROUTE53 = "route53",
Cloudflare = "cloudflare"
Cloudflare = "cloudflare",
DNSMadeEasy = "dns-made-easy"
}
export enum CaCapability {
+1 -1
View File
@@ -13,12 +13,12 @@ export {
export {
useGetAzureAdcsTemplates,
useGetCa,
useGetCaById,
useGetCaCert,
useGetCaCerts,
useGetCaCertTemplates,
useGetCaCrls,
useGetCaCsr,
useGetInternalCaById,
useListCasByProjectId,
useListCasByTypeAndProjectId,
useListExternalCasByProjectId
+14 -19
View File
@@ -27,21 +27,20 @@ import {
export const useUpdateCa = () => {
const queryClient = useQueryClient();
return useMutation<TUnifiedCertificateAuthority, object, TUpdateCertificateAuthorityDTO>({
mutationFn: async ({ caName, ...body }) => {
mutationFn: async ({ id, ...body }) => {
const { data } = await apiRequest.patch<TUnifiedCertificateAuthority>(
`/api/v1/pki/ca/${body.type}/${caName}`,
`/api/v1/cert-manager/ca/${body.type}/${id}`,
body
);
return data;
},
onSuccess: ({ projectId, type }, { caName }) => {
caKeys.getCaByNameAndProjectId(caName, projectId);
onSuccess: ({ projectId, type }, { id }) => {
queryClient.invalidateQueries({
queryKey: caKeys.listCasByTypeAndProjectId(type, projectId)
});
queryClient.invalidateQueries({
queryKey: caKeys.getCaByNameAndProjectId(caName, projectId)
queryKey: caKeys.getCaById(id)
});
// Invalidate external CAs list
queryClient.invalidateQueries({
@@ -56,7 +55,7 @@ export const useCreateCa = () => {
return useMutation<TUnifiedCertificateAuthority, object, TCreateCertificateAuthorityDTO>({
mutationFn: async (body) => {
const { data } = await apiRequest.post<TUnifiedCertificateAuthority>(
`/api/v1/pki/ca/${body.type}`,
`/api/v1/cert-manager/ca/${body.type}`,
body
);
return data;
@@ -76,14 +75,9 @@ export const useCreateCa = () => {
export const useDeleteCa = () => {
const queryClient = useQueryClient();
return useMutation<TUnifiedCertificateAuthority, object, TDeleteCertificateAuthorityDTO>({
mutationFn: async ({ caName, type, projectId }) => {
mutationFn: async ({ id, type }) => {
const { data } = await apiRequest.delete<TUnifiedCertificateAuthority>(
`/api/v1/pki/ca/${type}/${caName}`,
{
data: {
projectId
}
}
`/api/v1/cert-manager/ca/${type}/${id}`
);
return data;
},
@@ -104,7 +98,7 @@ export const useSignIntermediate = () => {
return useMutation<TSignIntermediateResponse, object, TSignIntermediateDTO>({
mutationFn: async (body) => {
const { data } = await apiRequest.post<TSignIntermediateResponse>(
`/api/v1/pki/ca/${body.caId}/sign-intermediate`,
`/api/v1/cert-manager/ca/internal/${body.caId}/sign-intermediate`,
body
);
return data;
@@ -117,13 +111,14 @@ export const useImportCaCertificate = (projectId: string) => {
return useMutation<TImportCaCertificateResponse, object, TImportCaCertificateDTO>({
mutationFn: async ({ caId, ...body }) => {
const { data } = await apiRequest.post<TImportCaCertificateResponse>(
`/api/v1/pki/ca/${caId}/import-certificate`,
`/api/v1/cert-manager/ca/internal/${caId}/import-certificate`,
body
);
return data;
},
onSuccess: (_, { caId }) => {
queryClient.invalidateQueries({ queryKey: projectKeys.getProjectCas({ projectId }) });
queryClient.invalidateQueries({ queryKey: caKeys.getCaById(caId) });
queryClient.invalidateQueries({ queryKey: caKeys.getCaCerts(caId) });
queryClient.invalidateQueries({ queryKey: caKeys.getCaCert(caId) });
queryClient.invalidateQueries({
@@ -133,7 +128,7 @@ export const useImportCaCertificate = (projectId: string) => {
});
};
// consider rename to issue certificate
// TODO: DEPRECATE
export const useCreateCertificate = () => {
const queryClient = useQueryClient();
return useMutation<TCreateCertificateResponse, object, TCreateCertificateDTO>({
@@ -157,7 +152,7 @@ export const useCreateCertificateV3 = (options?: { projectId?: string }) => {
return useMutation<TCreateCertificateV3Response, object, TCreateCertificateV3DTO>({
mutationFn: async (body) => {
const { data } = await apiRequest.post<TCreateCertificateV3Response>(
"/api/v3/pki/certificates/issue-certificate",
"/api/v1/cert-manager/certificates/issue-certificate",
body
);
return data;
@@ -185,7 +180,7 @@ export const useOrderCertificateWithProfile = () => {
return useMutation<TOrderCertificateResponse, object, TOrderCertificateDTO>({
mutationFn: async (body) => {
const { data } = await apiRequest.post<TOrderCertificateResponse>(
"/api/v3/pki/certificates/order-certificate",
"/api/v1/cert-manager/certificates/order-certificate",
body
);
return data;
@@ -203,7 +198,7 @@ export const useRenewCa = () => {
return useMutation<TRenewCaResponse, object, TRenewCaDTO>({
mutationFn: async (body) => {
const { data } = await apiRequest.post<TRenewCaResponse>(
`/api/v1/pki/ca/${body.caId}/renew`,
`/api/v1/cert-manager/ca/internal/${body.caId}/renew`,
body
);
return data;
+28 -29
View File
@@ -4,7 +4,11 @@ import { apiRequest } from "@app/config/request";
import { TCertificateTemplate } from "../certificateTemplates/types";
import { CaType } from "./enums";
import { TAzureAdCsTemplate, TCertificateAuthority, TUnifiedCertificateAuthority } from "./types";
import {
TAzureAdCsTemplate,
TInternalCertificateAuthority,
TUnifiedCertificateAuthority
} from "./types";
export const caKeys = {
getCaById: (caId: string) => [{ caId }, "ca"],
@@ -25,24 +29,16 @@ export const caKeys = {
]
};
export const useGetCa = ({
caName,
projectId,
type
}: {
caName: string;
projectId: string;
type: CaType;
}) => {
export const useGetCa = ({ caId, type }: { caId: string; type: CaType }) => {
return useQuery({
queryKey: caKeys.getCaByNameAndProjectId(caName, projectId),
queryKey: caKeys.getCaById(caId),
queryFn: async () => {
const { data } = await apiRequest.get<TUnifiedCertificateAuthority>(
`/api/v1/pki/ca/${type}/${caName}?projectId=${projectId}`
`/api/v1/cert-manager/ca/${type}/${caId}`
);
return data;
},
enabled: Boolean(caName && projectId && type)
enabled: Boolean(caId && type)
});
};
@@ -51,7 +47,7 @@ export const useListCasByTypeAndProjectId = (type: CaType, projectId: string) =>
queryKey: caKeys.listCasByTypeAndProjectId(type, projectId),
queryFn: async () => {
const { data } = await apiRequest.get<TUnifiedCertificateAuthority[]>(
`/api/v1/pki/ca/${type}?projectId=${projectId}`
`/api/v1/cert-manager/ca/${type}?projectId=${projectId}`
);
return data;
@@ -65,7 +61,7 @@ export const useListCasByProjectId = (projectId: string) => {
queryFn: async () => {
const { data } = await apiRequest.get<{
certificateAuthorities: TUnifiedCertificateAuthority[];
}>(`/api/v2/pki/ca?projectId=${projectId}`);
}>(`/api/v1/cert-manager/ca?projectId=${projectId}`);
return data.certificateAuthorities;
}
@@ -78,10 +74,10 @@ export const useListExternalCasByProjectId = (projectId: string) => {
queryFn: async () => {
const [acmeResponse, azureAdCsResponse] = await Promise.allSettled([
apiRequest.get<TUnifiedCertificateAuthority[]>(
`/api/v1/pki/ca/${CaType.ACME}?projectId=${projectId}`
`/api/v1/cert-manager/ca/${CaType.ACME}?projectId=${projectId}`
),
apiRequest.get<TUnifiedCertificateAuthority[]>(
`/api/v1/pki/ca/${CaType.AZURE_AD_CS}?projectId=${projectId}`
`/api/v1/cert-manager/ca/${CaType.AZURE_AD_CS}?projectId=${projectId}`
)
]);
@@ -100,14 +96,14 @@ export const useListExternalCasByProjectId = (projectId: string) => {
});
};
export const useGetCaById = (caId: string) => {
export const useGetInternalCaById = (caId: string) => {
return useQuery({
queryKey: caKeys.getCaById(caId),
queryFn: async () => {
const {
data: { ca }
} = await apiRequest.get<{ ca: TCertificateAuthority }>(`/api/v1/pki/ca/${caId}`);
return ca;
const { data } = await apiRequest.get<TInternalCertificateAuthority>(
`/api/v1/cert-manager/ca/internal/${caId}`
);
return data;
},
enabled: Boolean(caId)
});
@@ -124,7 +120,7 @@ export const useGetCaCerts = (caId: string) => {
serialNumber: string;
version: number;
}[]
>(`/api/v1/pki/ca/${caId}/ca-certificates`); // TODO: consider updating endpoint structure
>(`/api/v1/cert-manager/ca/internal/${caId}/ca-certificates`);
return data;
},
enabled: Boolean(caId)
@@ -139,7 +135,7 @@ export const useGetCaCert = (caId: string) => {
certificate: string;
certificateChain: string;
serialNumber: string;
}>(`/api/v1/pki/ca/${caId}/certificate`); // TODO: consider updating endpoint structure
}>(`/api/v1/cert-manager/ca/internal/${caId}/certificate`);
return data;
},
enabled: Boolean(caId)
@@ -154,7 +150,7 @@ export const useGetCaCsr = (caId: string) => {
data: { csr }
} = await apiRequest.get<{
csr: string;
}>(`/api/v1/pki/ca/${caId}/csr`);
}>(`/api/v1/cert-manager/ca/internal/${caId}/csr`);
return csr;
},
enabled: Boolean(caId)
@@ -170,13 +166,14 @@ export const useGetCaCrls = (caId: string) => {
id: string;
crl: string;
}[]
>(`/api/v1/pki/ca/${caId}/crls`);
>(`/api/v1/cert-manager/ca/internal/${caId}/crls`);
return data;
},
enabled: Boolean(caId)
});
};
// TODO: DEPRECATE
export const useGetCaCertTemplates = (caId: string) => {
return useQuery({
queryKey: caKeys.getCaCertTemplates(caId),
@@ -192,19 +189,21 @@ export const useGetCaCertTemplates = (caId: string) => {
export const useGetAzureAdcsTemplates = ({
caId,
projectId
projectId,
isAzureAdcsCa
}: {
caId: string;
projectId: string;
isAzureAdcsCa: boolean;
}) => {
return useQuery({
queryKey: caKeys.getAzureAdcsTemplates(caId, projectId),
queryFn: async () => {
const { data } = await apiRequest.get<{
templates: TAzureAdCsTemplate[];
}>(`/api/v1/pki/ca/azure-ad-cs/${caId}/templates?projectId=${projectId}`);
}>(`/api/v1/cert-manager/ca/azure-ad-cs/${caId}/templates?projectId=${projectId}`);
return data;
},
enabled: Boolean(caId && projectId)
enabled: Boolean(caId && projectId && isAzureAdcsCa)
});
};
+7 -5
View File
@@ -66,17 +66,19 @@ export type TUnifiedCertificateAuthority =
| TAzureAdCsCertificateAuthority
| TInternalCertificateAuthority;
export type TCreateCertificateAuthorityDTO = Omit<TUnifiedCertificateAuthority, "id">;
export type TCreateCertificateAuthorityDTO = Omit<
TUnifiedCertificateAuthority,
"id" | "enableDirectIssuance"
>;
export type TUpdateCertificateAuthorityDTO = Partial<TUnifiedCertificateAuthority> & {
caName: string;
projectId: string;
id: string;
type: CaType;
};
export type TDeleteCertificateAuthorityDTO = {
caName: string;
type: CaType;
id: string;
projectId: string;
type: CaType;
};
export type TCertificateAuthority = {
@@ -10,4 +10,4 @@ export {
useGetProfileCertificates,
useListCertificateProfiles
} from "./queries";
export type * from "./types";
export * from "./types";
@@ -17,7 +17,7 @@ export const useCreateCertificateProfile = () => {
mutationFn: async (data) => {
const { data: response } = await apiRequest.post<{
certificateProfile: TCertificateProfile;
}>("/api/v1/pki/certificate-profiles", data);
}>("/api/v1/cert-manager/certificate-profiles", data);
return response.certificateProfile;
},
onSuccess: (_, { projectId }) => {
@@ -35,7 +35,7 @@ export const useUpdateCertificateProfile = () => {
mutationFn: async ({ profileId, ...data }) => {
const { data: response } = await apiRequest.patch<{
certificateProfile: TCertificateProfile;
}>(`/api/v1/pki/certificate-profiles/${profileId}`, data);
}>(`/api/v1/cert-manager/certificate-profiles/${profileId}`, data);
return response.certificateProfile;
},
onSuccess: (profile, { profileId }) => {
@@ -56,7 +56,7 @@ export const useDeleteCertificateProfile = () => {
mutationFn: async ({ profileId }) => {
const { data: response } = await apiRequest.delete<{
certificateProfile: TCertificateProfile;
}>(`/api/v1/pki/certificate-profiles/${profileId}`);
}>(`/api/v1/cert-manager/certificate-profiles/${profileId}`);
return response.certificateProfile;
},
onSuccess: (profile, { profileId }) => {
@@ -71,7 +71,7 @@ export const useListCertificateProfiles = ({
const { data } = await apiRequest.get<{
certificateProfiles: TCertificateProfile[];
totalCount: number;
}>("/api/v1/pki/certificate-profiles", {
}>("/api/v1/cert-manager/certificate-profiles", {
params: {
projectId,
limit,
@@ -93,7 +93,7 @@ export const useGetCertificateProfileById = ({ profileId }: TGetCertificateProfi
queryFn: async () => {
const { data } = await apiRequest.get<{
certificateProfile: TCertificateProfileWithDetails;
}>(`/api/v1/pki/certificate-profiles/${profileId}`);
}>(`/api/v1/cert-manager/certificate-profiles/${profileId}`);
return data.certificateProfile;
},
enabled: Boolean(profileId)
@@ -109,7 +109,7 @@ export const useGetCertificateProfileBySlug = ({
queryFn: async () => {
const { data } = await apiRequest.get<{
certificateProfile: TCertificateProfile;
}>(`/api/v1/pki/certificate-profiles/slug/${slug}`, {
}>(`/api/v1/cert-manager/certificate-profiles/slug/${slug}`, {
params: { projectId }
});
return data.certificateProfile;
@@ -125,7 +125,7 @@ export const useRevealAcmeEabSecret = ({ profileId }: TRevealAcmeEabSecretDTO) =
const { data } = await apiRequest.get<{
eabKid: string;
eabSecret: string;
}>(`/api/v1/pki/certificate-profiles/${profileId}/acme/eab-secret/reveal`);
}>(`/api/v1/cert-manager/certificate-profiles/${profileId}/acme/eab-secret/reveal`);
return data;
},
enabled: Boolean(profileId)
@@ -144,7 +144,7 @@ export const useGetProfileCertificates = ({
queryFn: async () => {
const { data } = await apiRequest.get<{
certificates: TProfileCertificate[];
}>(`/api/v1/pki/certificate-profiles/${profileId}/certificates`, {
}>(`/api/v1/cert-manager/certificate-profiles/${profileId}/certificates`, {
params: {
offset,
limit,
@@ -1,23 +1,46 @@
export enum EnrollmentType {
API = "api",
EST = "est",
ACME = "acme"
}
export enum IssuerType {
CA = "ca",
SELF_SIGNED = "self-signed"
}
export type TCertificateProfile = {
id: string;
projectId: string;
caId: string;
caId: string | null;
certificateTemplateId: string;
slug: string;
description?: string;
enrollmentType: "api" | "est" | "acme";
enrollmentType: EnrollmentType;
issuerType: IssuerType;
estConfigId?: string;
apiConfigId?: string;
createdAt: string;
updatedAt: string;
externalConfigs?: Record<string, unknown> | null;
certificateAuthority?: {
id: string;
projectId?: string;
status: string;
name: string;
isExternal?: boolean;
externalType?: string | null;
};
};
export type TCertificateProfileWithDetails = TCertificateProfile & {
certificateAuthority?: {
id: string;
projectId: string;
projectId?: string;
status: string;
name: string;
isExternal?: boolean;
externalType?: string | null;
};
certificateTemplate?: {
id: string;
@@ -44,11 +67,12 @@ export type TCertificateProfileWithDetails = TCertificateProfile & {
export type TCreateCertificateProfileDTO = {
projectId: string;
caId: string;
caId?: string;
certificateTemplateId: string;
slug: string;
description?: string;
enrollmentType: "api" | "est" | "acme";
enrollmentType: EnrollmentType;
issuerType: IssuerType;
estConfig?: {
disableBootstrapCaValidation?: boolean;
passphrase: string;
@@ -59,12 +83,15 @@ export type TCreateCertificateProfileDTO = {
renewBeforeDays?: number;
};
acmeConfig?: unknown;
externalConfigs?: Record<string, unknown> | null;
};
export type TUpdateCertificateProfileDTO = {
profileId: string;
slug?: string;
description?: string;
enrollmentType?: EnrollmentType;
issuerType?: IssuerType;
estConfig?: {
disableBootstrapCaValidation?: boolean;
passphrase?: string;
@@ -75,6 +102,7 @@ export type TUpdateCertificateProfileDTO = {
renewBeforeDays?: number;
};
acmeConfig?: unknown;
externalConfigs?: Record<string, unknown> | null;
};
export type TDeleteCertificateProfileDTO = {
@@ -87,7 +115,9 @@ export type TListCertificateProfilesDTO = {
offset?: number;
search?: string;
includeConfigs?: boolean;
enrollmentType?: "api" | "est" | "acme";
enrollmentType?: EnrollmentType;
issuerType?: IssuerType;
caId?: string;
};
export type TGetCertificateProfileByIdDTO = {
@@ -21,6 +21,7 @@ import {
TUpdateEstConfigDTO
} from "./types";
// TODO: DEPRECATE
export const useCreateCertTemplate = () => {
const queryClient = useQueryClient();
return useMutation<TCertificateTemplate, object, TCreateCertificateTemplateDTO>({
@@ -40,6 +41,7 @@ export const useCreateCertTemplate = () => {
});
};
// TODO: DEPRECATE
export const useUpdateCertTemplate = () => {
const queryClient = useQueryClient();
return useMutation<TCertificateTemplate, object, TUpdateCertificateTemplateDTO>({
@@ -61,6 +63,7 @@ export const useUpdateCertTemplate = () => {
});
};
// TODO: DEPRECATE
export const useDeleteCertTemplate = () => {
const queryClient = useQueryClient();
return useMutation<TCertificateTemplate, object, TDeleteCertificateTemplateDTO>({
@@ -147,6 +150,7 @@ export const useDeleteCertTemplateV2 = () => {
});
};
// TODO: DEPRECATE
export const useCreateEstConfig = () => {
const queryClient = useQueryClient();
return useMutation<object, object, TCreateEstConfigDTO>({
@@ -165,6 +169,7 @@ export const useCreateEstConfig = () => {
});
};
// TODO: DEPRECATE
export const useUpdateEstConfig = () => {
const queryClient = useQueryClient();
return useMutation<object, object, TUpdateEstConfigDTO>({
@@ -193,7 +198,7 @@ export const useCreateCertificateTemplateV2WithPolicies = () => {
mutationFn: async (data) => {
const { data: response } = await apiRequest.post<{
certificateTemplate: TCertificateTemplateV2WithPolicies;
}>("/api/v2/certificate-templates", data);
}>("/api/v1/cert-manager/certificate-templates", data);
return response.certificateTemplate;
},
onSuccess: (_, { projectId }) => {
@@ -214,7 +219,7 @@ export const useUpdateCertificateTemplateV2WithPolicies = () => {
mutationFn: async ({ templateId, ...data }) => {
const { data: response } = await apiRequest.patch<{
certificateTemplate: TCertificateTemplateV2WithPolicies;
}>(`/api/v2/certificate-templates/${templateId}`, data);
}>(`/api/v1/cert-manager/certificate-templates/${templateId}`, data);
return response.certificateTemplate;
},
onSuccess: (template, { templateId }) => {
@@ -238,7 +243,7 @@ export const useDeleteCertificateTemplateV2WithPolicies = () => {
mutationFn: async ({ templateId }) => {
const { data: response } = await apiRequest.delete<{
certificateTemplate: TCertificateTemplateV2WithPolicies;
}>(`/api/v2/certificate-templates/${templateId}`);
}>(`/api/v1/cert-manager/certificate-templates/${templateId}`);
return response.certificateTemplate;
},
onSuccess: (template, { templateId }) => {
@@ -31,6 +31,7 @@ export const certTemplateKeys = {
getTemplateV2ById: (id: string) => ["cert-template-v2", id]
};
// TODO: DEPRECATE
export const useGetCertTemplate = (id: string) => {
return useQuery({
queryKey: certTemplateKeys.getCertTemplateById(id),
@@ -44,6 +45,7 @@ export const useGetCertTemplate = (id: string) => {
});
};
// TODO: DEPRECATE
export const useListCertificateTemplates = ({
limit = 100,
offset = 0,
@@ -67,6 +69,7 @@ export const useListCertificateTemplates = ({
});
};
// TODO: DEPRECATE
export const useGetEstConfig = (certificateTemplateId: string) => {
return useQuery({
queryKey: certTemplateKeys.getEstConfig(certificateTemplateId),
@@ -92,7 +95,7 @@ export const useListCertificateTemplatesV2 = ({
const { data } = await apiRequest.get<{
certificateTemplates: TCertificateTemplateV2WithPolicies[];
totalCount: number;
}>("/api/v2/certificate-templates", {
}>("/api/v1/cert-manager/certificate-templates", {
params: {
projectId,
limit,
@@ -113,7 +116,7 @@ export const useGetCertificateTemplateV2ById = ({
queryFn: async () => {
const { data } = await apiRequest.get<{
certificateTemplate: TCertificateTemplateV2WithPolicies;
}>(`/api/v2/certificate-templates/${templateId}`);
}>(`/api/v1/cert-manager/certificate-templates/${templateId}`);
return data.certificateTemplate;
},
enabled: Boolean(templateId)
@@ -13,23 +13,34 @@ import {
TRenewCertificateDTO,
TRenewCertificateResponse,
TRevokeCertDTO,
TUnifiedCertificateIssuanceDTO,
TUnifiedCertificateIssuanceResponse,
TUpdateRenewalConfigDTO
} from "./types";
export const useDeleteCert = () => {
const queryClient = useQueryClient();
return useMutation<TCertificate, object, TDeleteCertDTO>({
mutationFn: async ({ serialNumber }) => {
mutationFn: async ({ id }) => {
const {
data: { certificate }
} = await apiRequest.delete<{ certificate: TCertificate }>(
`/api/v1/pki/certificates/${serialNumber}`
`/api/v1/cert-manager/certificates/${id}`
);
return certificate;
},
onSuccess: (_, { projectSlug }) => {
onSuccess: (_, { projectId }) => {
queryClient.invalidateQueries({
queryKey: projectKeys.forProjectCertificates(projectSlug)
queryKey: ["certificate-profiles", "list"]
});
queryClient.invalidateQueries({
queryKey: pkiSubscriberKeys.allPkiSubscriberCertificates()
});
queryClient.invalidateQueries({
queryKey: projectKeys.allProjectCertificates()
});
queryClient.invalidateQueries({
queryKey: projectKeys.forProjectCertificates(projectId)
});
}
});
@@ -38,27 +49,29 @@ export const useDeleteCert = () => {
export const useRevokeCert = () => {
const queryClient = useQueryClient();
return useMutation<TCertificate, object, TRevokeCertDTO>({
mutationFn: async ({ serialNumber, revocationReason }) => {
mutationFn: async ({ id, revocationReason }) => {
const {
data: { certificate }
} = await apiRequest.post<{ certificate: TCertificate }>(
`/api/v1/pki/certificates/${serialNumber}/revoke`,
`/api/v1/cert-manager/certificates/${id}/revoke`,
{
revocationReason
}
);
return certificate;
},
onSuccess: (_, { projectSlug }) => {
onSuccess: (_, { projectId }) => {
queryClient.invalidateQueries({
queryKey: projectKeys.forProjectCertificates(projectSlug)
queryKey: ["certificate-profiles", "list"]
});
queryClient.invalidateQueries({
queryKey: pkiSubscriberKeys.allPkiSubscriberCertificates()
});
queryClient.invalidateQueries({
queryKey: ["certificate-profiles", "list"]
queryKey: projectKeys.allProjectCertificates()
});
queryClient.invalidateQueries({
queryKey: projectKeys.forProjectCertificates(projectId)
});
}
});
@@ -69,7 +82,7 @@ export const useImportCertificate = () => {
return useMutation<TImportCertificateResponse, object, TImportCertificateDTO>({
mutationFn: async (body) => {
const { data } = await apiRequest.post<TImportCertificateResponse>(
"/api/v1/pki/certificates/import-certificate",
"/api/v1/cert-manager/certificates/import-certificate",
body
);
return data;
@@ -87,7 +100,7 @@ export const useRenewCertificate = () => {
return useMutation<TRenewCertificateResponse, object, TRenewCertificateDTO>({
mutationFn: async ({ certificateId }) => {
const { data } = await apiRequest.post<TRenewCertificateResponse>(
`/api/v3/pki/certificates/${certificateId}/renew`,
`/api/v1/cert-manager/certificates/${certificateId}/renew`,
{}
);
return data;
@@ -120,7 +133,7 @@ export const useUpdateRenewalConfig = () => {
>({
mutationFn: async ({ certificateId, renewBeforeDays, enableAutoRenewal }) => {
const { data } = await apiRequest.patch<{ message: string; renewBeforeDays?: number }>(
`/api/v3/pki/certificates/${certificateId}/config`,
`/api/v1/cert-manager/certificates/${certificateId}/config`,
{ renewBeforeDays, enableAutoRenewal }
);
return data;
@@ -138,10 +151,10 @@ export const useUpdateRenewalConfig = () => {
export const useDownloadCertPkcs12 = () => {
return useMutation<void, object, TDownloadPkcs12DTO>({
mutationFn: async ({ serialNumber, projectSlug, password, alias }) => {
mutationFn: async ({ certificateId, projectSlug, password, alias }) => {
try {
const response = await apiRequest.post(
`/api/v1/pki/certificates/${serialNumber}/pkcs12`,
`/api/v1/cert-manager/certificates/${certificateId}/pkcs12`,
{
password,
alias
@@ -157,7 +170,7 @@ export const useDownloadCertPkcs12 = () => {
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `certificate-${serialNumber}.p12`;
link.download = `certificate-${certificateId}.p12`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
@@ -174,3 +187,31 @@ export const useDownloadCertPkcs12 = () => {
}
});
};
export const useUnifiedCertificateIssuance = () => {
const queryClient = useQueryClient();
return useMutation<TUnifiedCertificateIssuanceResponse, object, TUnifiedCertificateIssuanceDTO>({
mutationFn: async (body) => {
const { projectSlug, ...requestData } = body;
const { data } = await apiRequest.post<TUnifiedCertificateIssuanceResponse>(
"/api/v1/cert-manager/certificates",
requestData
);
return data;
},
onSuccess: (_, { projectSlug }) => {
queryClient.invalidateQueries({
queryKey: ["certificate-profiles", "list"]
});
queryClient.invalidateQueries({
queryKey: pkiSubscriberKeys.allPkiSubscriberCertificates()
});
queryClient.invalidateQueries({
queryKey: projectKeys.allProjectCertificates()
});
queryClient.invalidateQueries({
queryKey: projectKeys.forProjectCertificates(projectSlug)
});
}
});
};
@@ -7,7 +7,11 @@ import { TCertificate } from "./types";
export const certKeys = {
getCertById: (serialNumber: string) => [{ serialNumber }, "cert"],
getCertBody: (serialNumber: string) => [{ serialNumber }, "certBody"],
getCertBundle: (serialNumber: string) => [{ serialNumber }, "certBundle"]
getCertBundle: (serialNumber: string) => [{ serialNumber }, "certBundle"],
getCertificateRequest: (requestId: string, projectSlug: string) => [
{ requestId, projectSlug },
"certificateRequest"
]
};
export const useGetCert = (serialNumber: string) => {
+62 -5
View File
@@ -24,13 +24,13 @@ export type TCertificate = {
};
export type TDeleteCertDTO = {
projectSlug: string;
serialNumber: string;
id: string;
projectId: string;
};
export type TRevokeCertDTO = {
projectSlug: string;
serialNumber: string;
projectId: string;
id: string;
revocationReason: string;
};
@@ -64,6 +64,7 @@ export type TRenewCertificateResponse = {
serialNumber: string;
certificateId: string;
projectId: string;
certificateRequestId?: string;
};
export type TUpdateRenewalConfigDTO = {
@@ -74,8 +75,64 @@ export type TUpdateRenewalConfigDTO = {
};
export type TDownloadPkcs12DTO = {
serialNumber: string;
certificateId: string;
projectSlug: string;
password: string;
alias: string;
};
export type TUnifiedCertificateIssuanceDTO = {
projectSlug: string;
profileId: string;
projectId: string;
csr?: string;
attributes?: {
commonName?: string;
keyUsages?: string[];
extendedKeyUsages?: string[];
altNames?: Array<{
type: string;
value: string;
}>;
signatureAlgorithm: string;
keyAlgorithm: string;
subjectAlternativeNames?: Array<{
type: string;
value: string;
}>;
ttl: string;
notBefore?: string;
notAfter?: string;
};
removeRootsFromChain?: boolean;
};
export type TUnifiedCertificateResponse = {
certificate: {
certificate: string;
issuingCaCertificate: string;
certificateChain: string;
privateKey?: string;
serialNumber: string;
certificateId: string;
};
certificateRequestId: string;
};
export type TCertificateRequestResponse = {
certificateRequestId: string;
status: "pending" | "issued" | "failed";
projectId: string;
};
export type TUnifiedCertificateIssuanceResponse =
| TUnifiedCertificateResponse
| TCertificateRequestResponse;
export type TCertificateRequestDetails = {
status: "pending" | "issued" | "failed";
certificate: TCertificate | null;
errorMessage: string | null;
createdAt: string;
updatedAt: string;
};
+1 -1
View File
@@ -5,4 +5,4 @@ export {
useRemoveUserFromGroup,
useUpdateGroup
} from "./mutations";
export { useGetGroupById, useListGroupUsers } from "./queries";
export { useGetGroupById, useListGroupProjects, useListGroupUsers } from "./queries";
+82 -1
View File
@@ -2,7 +2,14 @@ import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { EFilterReturnedUsers, TGroup, TGroupUser } from "./types";
import { OrderByDirection } from "../generic/types";
import {
EFilterReturnedProjects,
EFilterReturnedUsers,
TGroup,
TGroupProject,
TGroupUser
} from "./types";
export const groupKeys = {
getGroupById: (groupId: string) => [{ groupId }, "group"] as const,
@@ -41,6 +48,29 @@ export const groupKeys = {
...groupKeys.forGroupUserMemberships(slug),
projectId,
{ offset, limit, search, filter }
] as const,
allGroupProjects: () => ["group-projects"] as const,
forGroupProjects: (groupId: string) => [...groupKeys.allGroupProjects(), groupId] as const,
specificGroupProjects: ({
groupId,
offset,
limit,
search,
filter,
orderBy,
orderDirection
}: {
groupId: string;
offset: number;
limit: number;
search: string;
filter?: EFilterReturnedProjects;
orderBy?: string;
orderDirection?: OrderByDirection;
}) =>
[
...groupKeys.forGroupProjects(groupId),
{ offset, limit, search, filter, orderBy, orderDirection }
] as const
};
@@ -148,3 +178,54 @@ export const useListProjectGroupUsers = ({
}
});
};
export const useListGroupProjects = ({
id,
offset = 0,
limit = 10,
search,
filter,
orderBy,
orderDirection
}: {
id: string;
offset: number;
limit: number;
search: string;
orderBy?: string;
orderDirection?: OrderByDirection;
filter?: EFilterReturnedProjects;
}) => {
return useQuery({
queryKey: groupKeys.specificGroupProjects({
groupId: id,
offset,
limit,
search,
filter,
orderBy,
orderDirection
}),
enabled: Boolean(id),
placeholderData: (previousData) => previousData,
queryFn: async () => {
const params = new URLSearchParams({
offset: String(offset),
limit: String(limit),
search,
...(filter && { filter }),
...(orderBy && { orderBy }),
...(orderDirection && { orderDirection })
});
const { data } = await apiRequest.get<{ projects: TGroupProject[]; totalCount: number }>(
`/api/v1/groups/${id}/projects`,
{
params
}
);
return data;
}
});
};
+14
View File
@@ -52,7 +52,21 @@ export type TGroupUser = {
joinedGroupAt: Date;
};
export type TGroupProject = {
id: string;
name: string;
slug: string;
description: string;
type: string;
joinedGroupAt: Date;
};
export enum EFilterReturnedUsers {
EXISTING_MEMBERS = "existingMembers",
NON_MEMBERS = "nonMembers"
}
export enum EFilterReturnedProjects {
ASSIGNED_PROJECTS = "assignedProjects",
UNASSIGNED_PROJECTS = "unassignedProjects"
}
+1 -3
View File
@@ -840,9 +840,7 @@ export type CreateTokenIdentityTokenAuthDTO = {
export type CreateTokenIdentityTokenAuthRes = {
accessToken: string;
tokenType: string;
expiresIn: number;
accessTokenMaxTTL: number;
tokenData: IdentityAccessToken;
};
export type UpdateTokenIdentityTokenAuthDTO = {
@@ -5,7 +5,6 @@ export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
}
export type TIdentityProjectPrivilege = {
projectMembershipId: string;
slug: string;
id: string;
createdAt: Date;
+24 -14
View File
@@ -6,6 +6,7 @@ import { kmsKeys } from "./queries";
import {
AddExternalKmsType,
ExternalKmsGcpSchemaType,
ExternalKmsProvider,
KmsGcpKeyFetchAuthType,
KmsType,
UpdateExternalKmsType
@@ -14,11 +15,12 @@ import {
export const useAddExternalKms = (orgId: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ name, description, provider }: AddExternalKmsType) => {
const { data } = await apiRequest.post("/api/v1/external-kms", {
mutationFn: async ({ name, description, configuration }: AddExternalKmsType) => {
const providerPath = configuration.type === ExternalKmsProvider.Aws ? "aws" : "gcp";
const { data } = await apiRequest.post(`/api/v1/external-kms/${providerPath}`, {
name,
description,
provider
configuration: configuration.inputs
});
return data;
@@ -29,21 +31,21 @@ export const useAddExternalKms = (orgId: string) => {
});
};
export const useUpdateExternalKms = (orgId: string) => {
export const useUpdateExternalKms = (orgId: string, provider: ExternalKmsProvider) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
kmsId,
name,
description,
provider
configuration
}: {
kmsId: string;
} & UpdateExternalKmsType) => {
const { data } = await apiRequest.patch(`/api/v1/external-kms/${kmsId}`, {
const { data } = await apiRequest.patch(`/api/v1/external-kms/${provider}/${kmsId}`, {
name,
description,
provider
configuration: configuration?.inputs
});
return data;
@@ -58,8 +60,8 @@ export const useUpdateExternalKms = (orgId: string) => {
export const useRemoveExternalKms = (orgId: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (kmsId: string) => {
const { data } = await apiRequest.delete(`/api/v1/external-kms/${kmsId}`);
mutationFn: async ({ kmsId, provider }: { kmsId: string; provider: ExternalKmsProvider }) => {
const { data } = await apiRequest.delete(`/api/v1/external-kms/${provider}/${kmsId}`);
return data;
},
@@ -130,11 +132,19 @@ export const useExternalKmsFetchGcpKeys = (orgId: string) => {
);
}
const { data } = await apiRequest.post("/api/v1/external-kms/gcp/keys", {
authMethod: credential ? KmsGcpKeyFetchAuthType.Credential : KmsGcpKeyFetchAuthType.Kms,
region: gcpRegion,
...rest
});
const requestBody = credential
? {
authMethod: KmsGcpKeyFetchAuthType.Credential,
region: gcpRegion,
credential
}
: {
authMethod: KmsGcpKeyFetchAuthType.Kms,
region: gcpRegion,
kmsId
};
const { data } = await apiRequest.post("/api/v1/external-kms/gcp/keys", requestBody);
return data;
},
+10 -6
View File
@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { Kms, KmsListEntry } from "./types";
import { ExternalKmsProvider, Kms, KmsListEntry } from "./types";
export const kmsKeys = {
getExternalKmsList: (orgId: string) => ["get-all-external-kms", { orgId }],
@@ -23,15 +23,19 @@ export const useGetExternalKmsList = (orgId: string, { enabled }: { enabled?: bo
});
};
export const useGetExternalKmsById = (kmsId: string) => {
export const useGetExternalKmsById = ({
kmsId,
provider
}: {
kmsId: string;
provider: ExternalKmsProvider;
}) => {
return useQuery({
queryKey: kmsKeys.getExternalKmsById(kmsId),
enabled: Boolean(kmsId),
queryFn: async () => {
const {
data: { externalKms }
} = await apiRequest.get<{ externalKms: Kms }>(`/api/v1/external-kms/${kmsId}`);
return externalKms;
const { data } = await apiRequest.get<Kms>(`/api/v1/external-kms/${provider}/${kmsId}`);
return data;
}
});
};
+8 -6
View File
@@ -8,12 +8,13 @@ export type Kms = {
description: string;
orgId: string;
name: string;
external: {
externalKms: {
id: string;
status: string;
statusDetails: string;
provider: string;
providerInput: Record<string, any>;
configuration: Record<string, any>;
credentialsHash?: string;
};
};
@@ -123,14 +124,14 @@ export const ExternalKmsInputSchema = z.discriminatedUnion("type", [
export const AddExternalKmsSchema = z.object({
name: slugSchema({ min: 1, field: "Alias" }),
description: z.string().trim().optional(),
provider: ExternalKmsInputSchema
configuration: ExternalKmsInputSchema
});
export type AddExternalKmsType = z.infer<typeof AddExternalKmsSchema>;
// we need separate schema for update because the credential field is not required on GCP
export const ExternalKmsUpdateInputSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(ExternalKmsProvider.Aws), inputs: ExternalKmsAwsSchema }),
z.object({ type: z.literal(ExternalKmsProvider.Aws), inputs: ExternalKmsAwsSchema.partial() }),
z.object({
type: z.literal(ExternalKmsProvider.Gcp),
inputs: ExternalKmsGcpSchema.pick({ gcpRegion: true, keyName: true })
@@ -144,9 +145,10 @@ export const UpdateExternalKmsSchema = z.object({
.min(1)
.refine((v) => slugify(v) === v, {
message: "Alias must be a valid slug"
}),
})
.optional(),
description: z.string().trim().optional(),
provider: ExternalKmsUpdateInputSchema
configuration: ExternalKmsUpdateInputSchema.optional()
});
export type UpdateExternalKmsType = z.infer<typeof UpdateExternalKmsSchema>;
+16
View File
@@ -1,3 +1,4 @@
// Resources
export enum PamResourceType {
Postgres = "postgres",
MySQL = "mysql",
@@ -18,9 +19,24 @@ export enum PamResourceType {
DynamoDB = "dynamodb"
}
export enum PamResourceOrderBy {
Name = "name"
}
// Sessions
export enum PamSessionStatus {
Starting = "starting",
Active = "active",
Ended = "ended",
Terminated = "terminated"
}
// Accounts
export enum PamAccountOrderBy {
Name = "name"
}
export enum PamAccountView {
Flat = "flat",
Nested = "nested"
}
+9 -9
View File
@@ -31,7 +31,7 @@ export const useCreatePamResource = () => {
return data.resource;
},
onSuccess: ({ projectId }) => {
queryClient.invalidateQueries({ queryKey: pamKeys.listResources(projectId) });
queryClient.invalidateQueries({ queryKey: pamKeys.listResources({ projectId }) });
}
});
};
@@ -48,7 +48,7 @@ export const useUpdatePamResource = () => {
return data.resource;
},
onSuccess: ({ projectId }) => {
queryClient.invalidateQueries({ queryKey: pamKeys.listResources(projectId) });
queryClient.invalidateQueries({ queryKey: pamKeys.listResources({ projectId }) });
}
});
};
@@ -64,7 +64,7 @@ export const useDeletePamResource = () => {
return data.resource;
},
onSuccess: ({ projectId }) => {
queryClient.invalidateQueries({ queryKey: pamKeys.listResources(projectId) });
queryClient.invalidateQueries({ queryKey: pamKeys.listResources({ projectId }) });
}
});
};
@@ -82,7 +82,7 @@ export const useCreatePamAccount = () => {
return data.account;
},
onSuccess: ({ projectId }) => {
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts(projectId) });
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts({ projectId }) });
}
});
};
@@ -99,7 +99,7 @@ export const useUpdatePamAccount = () => {
return data.account;
},
onSuccess: ({ projectId }) => {
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts(projectId) });
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts({ projectId }) });
}
});
};
@@ -115,7 +115,7 @@ export const useDeletePamAccount = () => {
return data.account;
},
onSuccess: ({ projectId }) => {
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts(projectId) });
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts({ projectId }) });
}
});
};
@@ -130,7 +130,7 @@ export const useCreatePamFolder = () => {
return data.folder;
},
onSuccess: ({ projectId }) => {
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts(projectId) });
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts({ projectId }) });
}
});
};
@@ -147,7 +147,7 @@ export const useUpdatePamFolder = () => {
return data.folder;
},
onSuccess: ({ projectId }) => {
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts(projectId) });
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts({ projectId }) });
}
});
};
@@ -163,7 +163,7 @@ export const useDeletePamFolder = () => {
return data.folder;
},
onSuccess: ({ projectId }) => {
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts(projectId) });
queryClient.invalidateQueries({ queryKey: pamKeys.listAccounts({ projectId }) });
}
});
};
+50 -20
View File
@@ -4,7 +4,14 @@ import { apiRequest } from "@app/config/request";
import { TPamResourceOption } from "./types/resource-options";
import { PamResourceType } from "./enums";
import { TPamAccount, TPamFolder, TPamResource, TPamSession } from "./types";
import {
TListPamAccountsDTO,
TListPamResourcesDTO,
TPamAccount,
TPamFolder,
TPamResource,
TPamSession
} from "./types";
export const pamKeys = {
all: ["pam"] as const,
@@ -12,14 +19,24 @@ export const pamKeys = {
account: () => [...pamKeys.all, "account"] as const,
session: () => [...pamKeys.all, "session"] as const,
listResourceOptions: () => [...pamKeys.resource(), "options"] as const,
listResources: (projectId: string) => [...pamKeys.resource(), "list", projectId],
listResources: ({ projectId, ...params }: TListPamResourcesDTO) => [
...pamKeys.resource(),
"list",
projectId,
params
],
getResource: (resourceType: string, resourceId: string) => [
...pamKeys.resource(),
"get",
resourceType,
resourceId
],
listAccounts: (projectId: string) => [...pamKeys.account(), "list", projectId],
listAccounts: ({ projectId, ...params }: TListPamAccountsDTO) => [
...pamKeys.account(),
"list",
projectId,
params
],
getSession: (sessionId: string) => [...pamKeys.session(), "get", sessionId],
listSessions: (projectId: string) => [...pamKeys.session(), "list", projectId]
};
@@ -49,28 +66,33 @@ export const useListPamResourceOptions = (
});
};
type TListPamResourcesResponse = {
resources: TPamResource[];
totalCount: number;
};
export const useListPamResources = (
projectId: string,
params: TListPamResourcesDTO,
options?: Omit<
UseQueryOptions<
TPamResource[],
TListPamResourcesResponse,
unknown,
TPamResource[],
TListPamResourcesResponse,
ReturnType<typeof pamKeys.listResources>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: pamKeys.listResources(projectId),
queryKey: pamKeys.listResources(params),
queryFn: async () => {
const { data } = await apiRequest.get<{ resources: TPamResource[] }>(
"/api/v1/pam/resources",
{ params: { projectId } }
);
const { data } = await apiRequest.get<TListPamResourcesResponse>("/api/v1/pam/resources", {
params
});
return data.resources;
return data;
},
placeholderData: (prev) => prev,
...options
});
};
@@ -98,28 +120,36 @@ export const useGetPamResourceById = (
};
// Accounts
type TListPamAccountsResponse = {
accounts: TPamAccount[];
folders: TPamFolder[];
totalCount: number;
folderId?: string;
folderPaths: Record<string, string>;
};
export const useListPamAccounts = (
projectId: string,
params: TListPamAccountsDTO,
options?: Omit<
UseQueryOptions<
{ accounts: TPamAccount[]; folders: TPamFolder[] },
TListPamAccountsResponse,
unknown,
{ accounts: TPamAccount[]; folders: TPamFolder[] },
TListPamAccountsResponse,
ReturnType<typeof pamKeys.listAccounts>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: pamKeys.listAccounts(projectId),
queryKey: pamKeys.listAccounts(params),
queryFn: async () => {
const { data } = await apiRequest.get<{ accounts: TPamAccount[]; folders: TPamFolder[] }>(
"/api/v1/pam/accounts",
{ params: { projectId } }
);
const { data } = await apiRequest.get<TListPamAccountsResponse>("/api/v1/pam/accounts", {
params
});
return data;
},
placeholderData: (prev) => prev,
...options
});
};
+51 -8
View File
@@ -1,13 +1,22 @@
import { PamResourceType, PamSessionStatus } from "../enums";
import { OrderByDirection } from "../../generic/types";
import {
PamAccountOrderBy,
PamAccountView,
PamResourceOrderBy,
PamResourceType,
PamSessionStatus
} from "../enums";
import { TMySQLAccount, TMySQLResource } from "./mysql-resource";
import { TPostgresAccount, TPostgresResource } from "./postgres-resource";
import { TSSHAccount, TSSHResource } from "./ssh-resource";
export * from "./mysql-resource";
export * from "./postgres-resource";
export * from "./ssh-resource";
export type TPamResource = TPostgresResource | TMySQLResource;
export type TPamResource = TPostgresResource | TMySQLResource | TSSHResource;
export type TPamAccount = TPostgresAccount | TMySQLAccount;
export type TPamAccount = TPostgresAccount | TMySQLAccount | TSSHAccount;
export type TPamFolder = {
id: string;
@@ -19,6 +28,22 @@ export type TPamFolder = {
updatedAt: string;
};
// Session log types
export type TPamCommandLog = {
input: string;
output: string;
timestamp: string;
};
export type TTerminalEvent = {
timestamp: string;
eventType: "input" | "output" | "resize" | "error";
data: string; // Base64 encoded binary data
elapsedTime: number; // Seconds since session start (for replay)
};
export type TPamSessionLog = TPamCommandLog | TTerminalEvent;
export type TPamSession = {
id: string;
projectId: string;
@@ -37,14 +62,20 @@ export type TPamSession = {
endedAt?: string | null;
createdAt: string;
updatedAt: string;
commandLogs: {
input: string;
output: string;
timestamp: string;
}[];
logs: TPamSessionLog[];
};
// Resource DTOs
export type TListPamResourcesDTO = {
projectId: string;
offset?: number;
limit?: number;
orderBy?: PamResourceOrderBy;
orderDirection?: OrderByDirection;
search?: string;
filterResourceTypes?: string;
};
export type TCreatePamResourceDTO = Pick<
TPamResource,
"name" | "connectionDetails" | "resourceType" | "gatewayId" | "projectId"
@@ -63,6 +94,18 @@ export type TDeletePamResourceDTO = {
};
// Account DTOs
export type TListPamAccountsDTO = {
projectId: string;
accountPath?: string | null;
accountView?: PamAccountView;
offset?: number;
limit?: number;
orderBy?: PamAccountOrderBy;
orderDirection?: OrderByDirection;
search?: string;
filterResourceIds?: string;
};
export type TCreatePamAccountDTO = Pick<
TPamAccount,
"name" | "description" | "credentials" | "projectId" | "resourceId" | "folderId"
@@ -0,0 +1,47 @@
import { PamResourceType } from "../enums";
import { TBasePamAccount } from "./base-account";
import { TBasePamResource } from "./base-resource";
export enum SSHAuthMethod {
Password = "password",
PublicKey = "public-key",
Certificate = "certificate"
}
export type TSSHConnectionDetails = {
host: string;
port: number;
};
export type TSSHPasswordCredentials = {
authMethod: SSHAuthMethod.Password;
username: string;
password: string;
};
export type TSSHPublicKeyCredentials = {
authMethod: SSHAuthMethod.PublicKey;
username: string;
privateKey: string;
};
export type TSSHCertificateCredentials = {
authMethod: SSHAuthMethod.Certificate;
username: string;
};
export type TSSHCredentials =
| TSSHPasswordCredentials
| TSSHPublicKeyCredentials
| TSSHCertificateCredentials;
// Resources
export type TSSHResource = TBasePamResource & { resourceType: PamResourceType.SSH } & {
connectionDetails: TSSHConnectionDetails;
rotationAccountCredentials?: TSSHCredentials | null;
};
// Accounts
export type TSSHAccount = TBasePamAccount & {
credentials: TSSHCredentials;
};
@@ -6,6 +6,7 @@ import { projectKeys } from "../projects";
import { pkiAlertKeys } from "./queries";
import { TCreatePkiAlertDTO, TDeletePkiAlertDTO, TPkiAlert, TUpdatePkiAlertDTO } from "./types";
// TODO: DEPRECATE
export const useCreatePkiAlert = () => {
const queryClient = useQueryClient();
return useMutation<TPkiAlert, object, TCreatePkiAlertDTO>({
@@ -19,6 +20,7 @@ export const useCreatePkiAlert = () => {
});
};
// TODO: DEPRECATE
export const useUpdatePkiAlert = () => {
const queryClient = useQueryClient();
return useMutation<TPkiAlert, object, TUpdatePkiAlertDTO>({
@@ -36,6 +38,7 @@ export const useUpdatePkiAlert = () => {
});
};
// TODO: DEPRECATE
export const useDeletePkiAlert = () => {
const queryClient = useQueryClient();
return useMutation<TPkiAlert, object, TDeletePkiAlertDTO>({
@@ -8,6 +8,7 @@ export const pkiAlertKeys = {
getPkiAlertById: (alertId: string) => [{ alertId }, "alert"]
};
// TODO: DEPRECATE
export const useGetPkiAlertById = (alertId: string) => {
return useQuery({
queryKey: pkiAlertKeys.getPkiAlertById(alertId),
@@ -11,7 +11,7 @@ export const useCreatePkiAlertV2 = () => {
return useMutation<TPkiAlertV2, unknown, TCreatePkiAlertV2>({
mutationFn: async (data) => {
const { data: response } = await apiRequest.post<{ alert: TPkiAlertV2 }>(
"/api/v2/pki/alerts",
"/api/v1/cert-manager/alerts",
data
);
return response.alert;
@@ -30,7 +30,7 @@ export const useUpdatePkiAlertV2 = () => {
return useMutation<TPkiAlertV2, unknown, TUpdatePkiAlertV2>({
mutationFn: async ({ alertId, ...data }) => {
const { data: response } = await apiRequest.patch<{ alert: TPkiAlertV2 }>(
`/api/v2/pki/alerts/${alertId}`,
`/api/v1/cert-manager/alerts/${alertId}`,
data
);
return response.alert;
@@ -52,7 +52,7 @@ export const useDeletePkiAlertV2 = () => {
return useMutation<TPkiAlertV2, unknown, TDeletePkiAlertV2>({
mutationFn: async ({ alertId }) => {
const { data } = await apiRequest.delete<{ alert: TPkiAlertV2 }>(
`/api/v2/pki/alerts/${alertId}`
`/api/v1/cert-manager/alerts/${alertId}`
);
return data.alert;
},
@@ -24,14 +24,16 @@ export const pkiAlertsV2Keys = {
};
const fetchPkiAlertsV2 = async (params: TGetPkiAlertsV2): Promise<TGetPkiAlertsV2Response> => {
const { data } = await apiRequest.get<TGetPkiAlertsV2Response>("/api/v2/pki/alerts", {
const { data } = await apiRequest.get<TGetPkiAlertsV2Response>("/api/v1/cert-manager/alerts", {
params
});
return data;
};
const fetchPkiAlertV2ById = async ({ alertId }: TGetPkiAlertV2ById): Promise<TPkiAlertV2> => {
const { data } = await apiRequest.get<{ alert: TPkiAlertV2 }>(`/api/v2/pki/alerts/${alertId}`);
const { data } = await apiRequest.get<{ alert: TPkiAlertV2 }>(
`/api/v1/cert-manager/alerts/${alertId}`
);
return data.alert;
};
@@ -40,7 +42,7 @@ const fetchPkiAlertV2MatchingCertificates = async (
): Promise<TGetPkiAlertV2MatchingCertificatesResponse> => {
const { alertId, ...queryParams } = params;
const { data } = await apiRequest.get<TGetPkiAlertV2MatchingCertificatesResponse>(
`/api/v2/pki/alerts/${alertId}/certificates`,
`/api/v1/cert-manager/alerts/${alertId}/certificates`,
{ params: queryParams }
);
return data;
@@ -50,7 +52,7 @@ const fetchPkiAlertV2CurrentMatchingCertificates = async (
params: TGetPkiAlertV2CurrentMatchingCertificates
): Promise<TGetPkiAlertV2CurrentMatchingCertificatesResponse> => {
const { data } = await apiRequest.post<TGetPkiAlertV2CurrentMatchingCertificatesResponse>(
"/api/v2/pki/alerts/preview/certificates",
"/api/v1/cert-manager/alerts/preview/certificates",
params
);
return data;
+4 -2
View File
@@ -1,6 +1,8 @@
export enum PkiSync {
AzureKeyVault = "azure-key-vault",
AwsCertificateManager = "aws-certificate-manager"
AwsCertificateManager = "aws-certificate-manager",
AwsSecretsManager = "aws-secrets-manager",
Chef = "chef"
}
export enum PkiSyncStatus {
@@ -12,7 +14,7 @@ export enum PkiSyncStatus {
export enum CertificateSyncStatus {
Pending = "pending",
Syncing = "syncing",
Running = "running",
Succeeded = "succeeded",
Failed = "failed"
}
+30 -14
View File
@@ -17,7 +17,10 @@ export const useCreatePkiSync = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ destination, ...params }: TCreatePkiSyncDTO) => {
const { data } = await apiRequest.post<TPkiSync>(`/api/v1/pki/syncs/${destination}`, params);
const { data } = await apiRequest.post<TPkiSync>(
`/api/v1/cert-manager/syncs/${destination}`,
params
);
return data;
},
@@ -31,7 +34,7 @@ export const useUpdatePkiSync = () => {
return useMutation({
mutationFn: async ({ syncId, projectId, destination, ...params }: TUpdatePkiSyncDTO) => {
const { data } = await apiRequest.patch<TPkiSync>(
`/api/v1/pki/syncs/${destination}/${syncId}`,
`/api/v1/cert-manager/syncs/${destination}/${syncId}`,
params,
{ params: { projectId } }
);
@@ -49,9 +52,12 @@ export const useDeletePkiSync = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ syncId, projectId, destination }: TDeletePkiSyncDTO) => {
const { data } = await apiRequest.delete(`/api/v1/pki/syncs/${destination}/${syncId}`, {
params: { projectId }
});
const { data } = await apiRequest.delete(
`/api/v1/cert-manager/syncs/${destination}/${syncId}`,
{
params: { projectId }
}
);
return data;
},
@@ -66,7 +72,9 @@ export const useTriggerPkiSyncSyncCertificates = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ syncId, destination }: TTriggerPkiSyncSyncCertificatesDTO) => {
const { data } = await apiRequest.post(`/api/v1/pki/syncs/${destination}/${syncId}/sync`);
const { data } = await apiRequest.post(
`/api/v1/cert-manager/syncs/${destination}/${syncId}/sync`
);
return data;
},
@@ -111,7 +119,9 @@ export const useTriggerPkiSyncImportCertificates = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ syncId, destination }: TTriggerPkiSyncImportCertificatesDTO) => {
const { data } = await apiRequest.post(`/api/v1/pki/syncs/${destination}/${syncId}/import`);
const { data } = await apiRequest.post(
`/api/v1/cert-manager/syncs/${destination}/${syncId}/import`
);
return data;
},
@@ -157,7 +167,7 @@ export const useTriggerPkiSyncRemoveCertificates = () => {
return useMutation({
mutationFn: async ({ syncId, destination }: TTriggerPkiSyncRemoveCertificatesDTO) => {
const { data } = await apiRequest.post(
`/api/v1/pki/syncs/${destination}/${syncId}/remove-certificates`
`/api/v1/cert-manager/syncs/${destination}/${syncId}/remove-certificates`
);
return data;
@@ -209,9 +219,12 @@ export const useAddCertificatesToPkiSync = () => {
pkiSyncId: string;
certificateIds: string[];
}) => {
const { data } = await apiRequest.post(`/api/v1/pki/syncs/${pkiSyncId}/certificates`, {
certificateIds
});
const { data } = await apiRequest.post(
`/api/v1/cert-manager/syncs/${pkiSyncId}/certificates`,
{
certificateIds
}
);
return data;
},
@@ -231,9 +244,12 @@ export const useRemoveCertificatesFromPkiSync = () => {
pkiSyncId: string;
certificateIds: string[];
}) => {
const { data } = await apiRequest.delete(`/api/v1/pki/syncs/${pkiSyncId}/certificates`, {
data: { certificateIds }
});
const { data } = await apiRequest.delete(
`/api/v1/cert-manager/syncs/${pkiSyncId}/certificates`,
{
data: { certificateIds }
}
);
return data;
},
+6 -4
View File
@@ -37,7 +37,9 @@ export const usePkiSyncOptions = (
return useQuery({
queryKey: pkiSyncKeys.options(),
queryFn: async () => {
const { data } = await apiRequest.get<TListPkiSyncOptions>("/api/v1/pki/syncs/options");
const { data } = await apiRequest.get<TListPkiSyncOptions>(
"/api/v1/cert-manager/syncs/options"
);
return data.pkiSyncOptions;
},
@@ -58,7 +60,7 @@ export const fetchPkiSyncsByProjectId = async (projectId: string, certificateId?
params.certificateId = certificateId;
}
const { data } = await apiRequest.get<TListPkiSyncs>("/api/v1/pki/syncs", {
const { data } = await apiRequest.get<TListPkiSyncs>("/api/v1/cert-manager/syncs", {
params
});
@@ -110,7 +112,7 @@ export const useGetPkiSync = (
return useQuery({
queryKey: pkiSyncKeys.byId(syncId, projectId),
queryFn: async () => {
const { data } = await apiRequest.get<TPkiSync>(`/api/v1/pki/syncs/${syncId}`, {
const { data } = await apiRequest.get<TPkiSync>(`/api/v1/cert-manager/syncs/${syncId}`, {
params: { projectId }
});
@@ -138,7 +140,7 @@ export const useListPkiSyncCertificates = (
return useQuery({
queryKey: pkiSyncKeys.certificates(syncId, { offset, limit }),
queryFn: async () => {
const { data } = await apiRequest.get(`/api/v1/pki/syncs/${syncId}/certificates`, {
const { data } = await apiRequest.get(`/api/v1/cert-manager/syncs/${syncId}/certificates`, {
params: { offset, limit }
});
return {
@@ -0,0 +1,29 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { PkiSync } from "../enums";
import { TRootPkiSync } from "./common";
export type TAwsSecretsManagerFieldMappings = {
certificate: string;
privateKey: string;
certificateChain: string;
caCertificate: string;
};
export type TAwsSecretsManagerPkiSync = TRootPkiSync & {
destination: PkiSync.AwsSecretsManager;
destinationConfig: {
region: string;
keyId?: string;
};
connection: {
app: AppConnection.AWS;
name: string;
id: string;
};
syncOptions: TRootPkiSync["syncOptions"] & {
fieldMappings?: TAwsSecretsManagerFieldMappings;
preserveSecretOnRenewal?: boolean;
updateExistingCertificates?: boolean;
};
};
@@ -0,0 +1,16 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { PkiSync } from "../enums";
import { TRootPkiSync } from "./common";
export type TChefPkiSync = TRootPkiSync & {
destination: PkiSync.Chef;
destinationConfig: {
dataBagName: string;
};
connection: {
app: AppConnection.Chef;
name: string;
id: string;
};
};
@@ -2,11 +2,23 @@ import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { CertificateSyncStatus, PkiSyncStatus } from "../enums";
export type TChefFieldMappings = {
certificate: string;
privateKey: string;
certificateChain: string;
caCertificate: string;
};
export type RootPkiSyncOptions = {
canImportCertificates: boolean;
canRemoveCertificates: boolean;
certificateNamePrefix?: string;
certificateNameSchema?: string;
preserveArn?: boolean;
enableVersioning?: boolean;
preserveItemOnRenewal?: boolean;
updateExistingCertificates?: boolean;
fieldMappings?: TChefFieldMappings;
};
export type TRootPkiSync = {
+21 -1
View File
@@ -1,7 +1,9 @@
import { PkiSync } from "@app/hooks/api/pkiSyncs";
import { TAwsCertificateManagerPkiSync } from "./aws-certificate-manager-sync";
import { TAwsSecretsManagerPkiSync } from "./aws-secrets-manager-sync";
import { TAzureKeyVaultPkiSync } from "./azure-key-vault-sync";
import { TChefPkiSync } from "./chef-sync";
export type TPkiSyncOption = {
name: string;
@@ -16,7 +18,11 @@ export type TPkiSyncOption = {
minCertificateNameLength?: number;
};
export type TPkiSync = TAzureKeyVaultPkiSync | TAwsCertificateManagerPkiSync;
export type TPkiSync =
| TAzureKeyVaultPkiSync
| TAwsCertificateManagerPkiSync
| TAwsSecretsManagerPkiSync
| TChefPkiSync;
export type TListPkiSyncs = { pkiSyncs: TPkiSync[] };
@@ -31,6 +37,17 @@ type TCreatePkiSyncDTOBase = {
canRemoveCertificates: boolean;
certificateNamePrefix?: string;
certificateNameSchema?: string;
preserveArn?: boolean;
enableVersioning?: boolean;
preserveItemOnRenewal?: boolean;
updateExistingCertificates?: boolean;
preserveSecretOnRenewal?: boolean;
fieldMappings?: {
certificate: string;
privateKey: string;
certificateChain: string;
caCertificate: string;
};
};
isAutoSyncEnabled: boolean;
subscriberId?: string | null;
@@ -43,6 +60,7 @@ export type TCreatePkiSyncDTO = TCreatePkiSyncDTOBase & {
destinationConfig: {
vaultBaseUrl?: string;
region?: string;
dataBagName?: string;
};
};
@@ -76,5 +94,7 @@ export type TTriggerPkiSyncRemoveCertificatesDTO = {
};
export * from "./aws-certificate-manager-sync";
export * from "./aws-secrets-manager-sync";
export * from "./azure-key-vault-sync";
export * from "./chef-sync";
export * from "./common";
@@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { groupKeys } from "../groups/queries";
import { userKeys } from "../users/query-keys";
import { projectKeys } from "./query-keys";
import {
@@ -30,10 +31,11 @@ export const useAddGroupToWorkspace = () => {
return groupMembership;
},
onSuccess: (_, { projectId }) => {
onSuccess: (_, { projectId, groupId }) => {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectGroupMemberships(projectId)
});
queryClient.invalidateQueries({ queryKey: groupKeys.forGroupProjects(groupId) });
}
});
};
@@ -77,11 +79,13 @@ export const useDeleteGroupFromWorkspace = () => {
} = await apiRequest.delete(`/api/v1/projects/${projectId}/groups/${groupId}`);
return groupMembership;
},
onSuccess: (_, { projectId, username }) => {
onSuccess: (_, { projectId, username, groupId }) => {
queryClient.invalidateQueries({
queryKey: projectKeys.getProjectGroupMemberships(projectId)
});
queryClient.invalidateQueries({ queryKey: groupKeys.forGroupProjects(groupId) });
if (username) {
queryClient.invalidateQueries({ queryKey: userKeys.listUserGroupMemberships(username) });
}
+6 -3
View File
@@ -191,12 +191,15 @@ export const fetchWorkspaceIntegrations = async (projectId: string) => {
return data.integrations;
};
export const useGetWorkspaceIntegrations = (projectId: string) =>
export const useGetWorkspaceIntegrations = (
projectId: string,
options?: { enabled?: boolean; refetchInterval?: number | false }
) =>
useQuery({
queryKey: projectKeys.getProjectIntegrations(projectId),
queryFn: () => fetchWorkspaceIntegrations(projectId),
enabled: Boolean(projectId),
refetchInterval: 4000
enabled: Boolean(projectId) && (options?.enabled ?? true),
refetchInterval: options?.refetchInterval ?? 4000
});
export const createWorkspace = (
+1 -1
View File
@@ -338,7 +338,7 @@ export const clearSession = (keepQueryClient?: boolean) => {
sessionStorage.removeItem(SessionStorageKeys.CLI_TERMINAL_TOKEN);
if (!keepQueryClient) {
qc.clear(); // Clear React Query cache
qc.invalidateQueries();
}
};
+10 -14
View File
@@ -1,9 +1,5 @@
@import "tailwindcss";
@import "@fontsource/inter/400.css" layer(base);
@import "@fontsource/inter/500.css" layer(base);
@import "@fontsource/inter/700.css" layer(base);
@source not "../public";
/*
@@ -29,11 +25,11 @@
}
:root {
font-family: var(--font-inter);
--foreground: oklch(0.985 0 0);
--background: oklch(0.145 0 0);
font-family: var(--font-inter);
--foreground: oklch(0.985 0 0);
--background: oklch(0.145 0 0);
--toastify-color-dark: var(--color-mineshaft-700);
--toastify-color-dark: var(--color-mineshaft-700);
}
@theme {
@@ -45,16 +41,16 @@
--color-background: #19191c;
--color-foreground: white;
--color-success: #2ecc71;
--color-info: #34c2db;
--color-info: #63b0bd;
--color-warning: #f1c40f;
--color-danger: #e74c3c;
--color-org: #30B3FF;
--color-org: #30b3ff;
--color-sub-org: #96ff59;
--color-project: #e0ed34;
--color-neutral: #adaeb0;
/*legacy color schema */
--color-org-v1: #30B3FF;
--color-org-v1: #30b3ff;
--color-namespace-v1: #96ff59;
/* Primary */
@@ -418,15 +414,15 @@
}
.Toastify__toast {
@apply rounded-md;
@apply rounded-md;
}
.Toastify__toast-body {
@apply items-start;
@apply items-start;
}
.Toastify__toast-icon {
@apply w-4 pt-1;
@apply w-4 pt-1;
}
.tags-conic-bg {
@@ -1,6 +1,3 @@
import { useTranslation } from "react-i18next";
import { faMobile } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Outlet } from "@tanstack/react-router";
import { Banner } from "@app/components/page-frames/Banner";
@@ -15,7 +12,6 @@ import { InsecureConnectionBanner } from "../OrganizationLayout/components/Insec
import { AdminNavBar } from "./AdminNavBar";
export const AdminLayout = () => {
const { t } = useTranslation();
const { config } = useServerConfig();
const { data: serverDetails, isLoading } = useFetchServerStatus();
const { subscription } = useSubscription();
@@ -26,7 +22,7 @@ export const AdminLayout = () => {
<>
<Banner />
<div
className={`dark hidden ${containerHeight} w-full flex-col overflow-x-hidden bg-bunker-800 transition-all md:flex`}
className={`dark ${containerHeight} flex w-full flex-col overflow-x-hidden bg-bunker-800 transition-all`}
>
<Navbar />
{!isLoading && !serverDetails?.redisConfigured && <RedisBanner />}
@@ -40,12 +36,6 @@ export const AdminLayout = () => {
</div>
</div>
</div>
<div className="z-200 flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden">
<FontAwesomeIcon icon={faMobile} className="mb-8 text-7xl text-gray-300" />
<p className="max-w-sm px-6 text-center text-lg text-gray-200">
{` ${t("common.no-mobile")} `}
</p>
</div>
<Banner />
</>
);
@@ -14,6 +14,7 @@ import { Link, useMatchRoute } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Tab, TabList, Tabs, Tooltip } from "@app/components/v2";
import { useOrganization } from "@app/context";
const generalTabs = [
{
@@ -60,6 +61,7 @@ const generalTabs = [
export const AdminNavBar = () => {
const matchRoute = useMatchRoute();
const { currentOrg } = useOrganization();
return (
<div className="border-b border-mineshaft-600 bg-mineshaft-900">
@@ -74,7 +76,7 @@ export const AdminNavBar = () => {
<Tabs value="selected">
<TabList className="border-b-0">
<Tooltip position="bottom" content="Back to organization">
<Link to="/organization/projects">
<Link to="/organizations/$orgId/projects" params={{ orgId: currentOrg.id }}>
<Tab value="back">
<FontAwesomeIcon icon={faArrowLeft} />
</Tab>
+14 -8
View File
@@ -2,19 +2,20 @@ import { Link, Outlet, useLocation } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Tab, TabList, Tabs } from "@app/components/v2";
import { useProject, useProjectPermission } from "@app/context";
import { useOrganization, useProject, useProjectPermission } from "@app/context";
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
export const KmsLayout = () => {
const { currentProject } = useProject();
const { currentOrg } = useOrganization();
const { assumedPrivilegeDetails } = useProjectPermission();
const location = useLocation();
return (
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="border-b border-mineshaft-600 bg-mineshaft-900">
<div className="dark flex h-full w-full flex-col overflow-x-hidden bg-mineshaft-900">
<div className="border-y border-t-project/10 border-b-project/5 bg-gradient-to-b from-project/[0.075] to-project/[0.025] px-4 pt-0.5">
<motion.div
key="menu-project-items"
initial={{ x: -150 }}
@@ -27,24 +28,27 @@ export const KmsLayout = () => {
<Tabs value="selected">
<TabList className="border-b-0">
<Link
to="/projects/kms/$projectId/overview"
to="/organizations/$orgId/projects/kms/$projectId/overview"
params={{
orgId: currentOrg.id,
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Overview</Tab>}
</Link>
<Link
to="/projects/kms/$projectId/kmip"
to="/organizations/$orgId/projects/kms/$projectId/kmip"
params={{
orgId: currentOrg.id,
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>KMIP</Tab>}
</Link>
<Link
to="/projects/kms/$projectId/access-management"
to="/organizations/$orgId/projects/kms/$projectId/access-management"
params={{
orgId: currentOrg.id,
projectId: currentProject.id
}}
>
@@ -62,16 +66,18 @@ export const KmsLayout = () => {
)}
</Link>
<Link
to="/projects/kms/$projectId/audit-logs"
to="/organizations/$orgId/projects/kms/$projectId/audit-logs"
params={{
orgId: currentOrg.id,
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Audit Logs</Tab>}
</Link>
<Link
to="/projects/kms/$projectId/settings"
to="/organizations/$orgId/projects/kms/$projectId/settings"
params={{
orgId: currentOrg.id,
projectId: currentProject.id
}}
>
@@ -1,6 +1,3 @@
import { useTranslation } from "react-i18next";
import { faMobile } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Outlet, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
@@ -27,8 +24,6 @@ export const OrganizationLayout = () => {
const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const);
const { t } = useTranslation();
const containerHeight = config.pageFrameContent ? "h-[94vh]" : "h-screen";
const { data: serverDetails, isLoading } = useFetchServerStatus();
@@ -38,7 +33,7 @@ export const OrganizationLayout = () => {
<>
<Banner />
<div
className={`dark hidden ${containerHeight} w-full flex-col overflow-x-hidden bg-bunker-800 transition-all md:flex`}
className={`dark ${containerHeight} flex w-full flex-col overflow-x-hidden bg-bunker-800 transition-all`}
>
<Navbar />
<div className="flex grow flex-col overflow-y-hidden">
@@ -61,12 +56,6 @@ export const OrganizationLayout = () => {
isOpen={popUp?.createOrg?.isOpen}
onClose={() => handlePopUpToggle("createOrg", false)}
/>
<div className="z-200 flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden">
<FontAwesomeIcon icon={faMobile} className="mb-8 text-7xl text-gray-300" />
<p className="max-w-sm px-6 text-center text-lg text-gray-200">
{` ${t("common.no-mobile")} `}
</p>
</div>
<Banner />
</>
);
@@ -16,18 +16,21 @@ import {
faSignOut,
faToolbox,
faUser,
faUserCog,
faUserPlus,
faUsers
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { Link, useLocation, useNavigate, useRouter, useRouterState } from "@tanstack/react-router";
import { Link, useLocation, useNavigate, useRouter } from "@tanstack/react-router";
import { ChevronRight, UserPlusIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { Mfa } from "@app/components/auth/Mfa";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import SecurityClient from "@app/components/utilities/SecurityClient";
import {
BreadcrumbContainer,
Button,
DropdownMenu,
DropdownMenuContent,
@@ -39,12 +42,17 @@ import {
IconButton,
Modal,
ModalContent,
TBreadcrumbFormat,
Tooltip
} from "@app/components/v2";
import { Badge, InstanceIcon, OrgIcon, SubOrgIcon } from "@app/components/v3";
import { envConfig } from "@app/config/env";
import { useOrganization, useSubscription, useUser } from "@app/context";
import {
OrgPermissionActions,
OrgPermissionSubjects,
useOrganization,
useSubscription,
useUser
} from "@app/context";
import { isInfisicalCloud } from "@app/helpers/platform";
import { useToggle } from "@app/hooks";
import {
@@ -59,6 +67,7 @@ import { MfaMethod } from "@app/hooks/api/auth/types";
import { getAuthToken } from "@app/hooks/api/reactQuery";
import { Organization, SubscriptionPlan } from "@app/hooks/api/types";
import { AuthMethod } from "@app/hooks/api/users/types";
import { ProjectSelect } from "@app/layouts/ProjectLayout/components/ProjectSelect";
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
import { ServerAdminsPanel } from "../ServerAdminsPanel/ServerAdminsPanel";
@@ -173,13 +182,7 @@ export const Navbar = () => {
}
}, [subscription, isBillingPage, isModalIntrusive]);
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
const breadcrumbs = matches && "breadcrumbs" in matches ? matches.breadcrumbs : undefined;
const handleOrgChange = async (orgId: string) => {
queryClient.removeQueries({ queryKey: authKeys.getAuthToken });
queryClient.removeQueries({ queryKey: projectKeys.getAllUserProjects() });
const { token, isMfaEnabled, mfaMethod } = await selectOrganization({
organizationId: orgId
});
@@ -196,6 +199,8 @@ export const Navbar = () => {
await router.invalidate();
await navigateUserToOrg(navigate, orgId);
queryClient.removeQueries({ queryKey: subOrgQuery.queryKey });
queryClient.removeQueries({ queryKey: authKeys.getAuthToken });
queryClient.removeQueries({ queryKey: projectKeys.getAllUserProjects() });
};
const { mutateAsync } = useGetOrgTrialUrl();
@@ -239,7 +244,9 @@ export const Navbar = () => {
const isServerAdminPanel = location.pathname.startsWith("/admin");
const isOrgScope = location.pathname.startsWith("/organization"); // TODO: scott/akhil is this adequate?
const isProjectScope =
location.pathname.startsWith(`/organizations/${currentOrg.id}/projects`) &&
location.pathname !== `/organizations/${currentOrg.id}/projects`;
const handleOrgNav = async (org: Organization) => {
if (currentOrg?.id === org.id) return;
@@ -269,64 +276,59 @@ export const Navbar = () => {
};
return (
<div className="z-10 flex min-h-12 items-center bg-mineshaft-900 px-4 pt-1">
<div className="mr-auto flex items-center overflow-hidden">
<div className="shrink-0">
<Link to="/organization/projects">
<div className="z-10 flex min-h-12 items-center bg-mineshaft-900 px-4">
<div className="mr-auto flex h-full min-w-34 items-center">
<div className="mt-0.5 shrink-0">
<Link to="/organizations/$orgId/projects" params={{ orgId: currentOrg.id }}>
<img alt="infisical logo" src="/images/logotransparent.png" className="h-4" />
</Link>
</div>
<p className="pr-3 pl-1 text-lg text-mineshaft-400/70">/</p>
<ChevronRight size={18} className="mx-3 mt-[3px] text-mineshaft-400/70" />
{isServerAdminPanel ? (
<>
<Link
to="/admin"
className="group flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary"
>
<InstanceIcon className="size-3.5 text-xs text-bunker-300" />
<div className="whitespace-nowrap">Server Console</div>
</Link>
<p className="pr-3 pl-3 text-lg text-mineshaft-400/70">/</p>
{breadcrumbs ? (
// scott: remove /admin as we show server console above
<BreadcrumbContainer breadcrumbs={breadcrumbs.slice(1) as TBreadcrumbFormat[]} />
) : null}
</>
<Link
to="/admin"
className="group flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary"
>
<InstanceIcon className="size-3.5 text-xs text-bunker-300" />
<div className="whitespace-nowrap">Server Console</div>
</Link>
) : (
<>
<div className="flex items-center overflow-hidden">
<div
className={twMerge(
"relative flex min-w-16 items-center self-end rounded-t-md border-x border-t pt-1.5 pr-2 pb-2.5 pl-3",
!isProjectScope && !isSubOrganization
? "border-org/15 bg-gradient-to-b from-org/10 to-org/[0.075]"
: "border-transparent"
)}
>
{/* scott: the below is used to hide the top border from the org nav bar */}
{!isProjectScope && !isSubOrganization && (
<div className="absolute -bottom-px left-0 h-px w-full bg-mineshaft-900">
<div className="h-full bg-org/[0.075]" />
</div>
)}
<DropdownMenu modal={false} open={isOrgSelectOpen} onOpenChange={setIsOrgSelectOpen}>
<div className="group flex cursor-pointer items-center gap-2 overflow-hidden text-sm text-white transition-all duration-100 hover:text-primary">
<Badge
asChild
variant="org"
isTruncatable
// TODO(scott): either add badge size/style variant or create designated component for namespace/org nav bar
className={twMerge(
"gap-x-1.5 text-sm",
(!isOrgScope || isSubOrganization) &&
"bg-transparent text-mineshaft-200 hover:!bg-transparent hover:underline [&>svg]:!text-org"
)}
<div className="group mr-1 flex min-w-0 cursor-pointer items-center gap-2 overflow-hidden text-sm text-white transition-all duration-100">
<button
className="flex cursor-pointer items-center gap-x-2 truncate whitespace-nowrap"
type="button"
onClick={async () => {
navigate({
to: "/organizations/$orgId/projects",
params: { orgId: currentOrg.id }
});
if (isSubOrganization) {
await router.invalidate({ sync: true }).catch(() => null);
}
}}
>
<button
type="button"
onClick={async () => {
navigate({
to: "/organization/projects",
search: (search) => ({ ...search, subOrganization: undefined })
});
if (isSubOrganization) {
await router.invalidate({ sync: true }).catch(() => null);
}
}}
>
<OrgIcon className="size-[12px]" />
<span>{currentOrg?.name}</span>
</button>
</Badge>
<div className="mr-1 rounded-sm border border-mineshaft-500 px-1 text-xs text-bunker-300 no-underline!">
{getPlan(subscription)}
</div>
<OrgIcon className={twMerge("size-[14px] shrink-0 text-org")} />
<span className="truncate">{currentOrg?.name}</span>
<Badge variant="org" className="hidden lg:inline-flex">
Organization
</Badge>
</button>
{subscription.cardDeclined && (
<Tooltip
content={`Your payment could not be processed${subscription.cardDeclinedReason ? `: ${subscription.cardDeclinedReason}` : ""}. Please update your payment method to continue enjoying premium features.`}
@@ -397,8 +399,8 @@ export const Navbar = () => {
<DropdownMenuItem
onClick={async () => {
navigate({
to: "/organization/projects",
search: (prev) => ({ ...prev, subOrganization: subOrg.name })
to: "/organizations/$orgId/projects",
params: { orgId: subOrg.id }
});
await router.invalidate({ sync: true }).catch(() => null);
}}
@@ -467,11 +469,11 @@ export const Navbar = () => {
// TODO(scott): either add badge size/style variant or create designated component for namespace/org nav bar
className={twMerge(
"gap-x-1.5 text-sm",
!isOrgScope &&
"bg-transparent text-mineshaft-200 hover:!bg-transparent hover:underline [&>svg]:!text-sub-org"
isProjectScope &&
"min-w-6 bg-transparent text-mineshaft-200 hover:!bg-transparent hover:underline [&>svg]:!text-sub-org"
)}
>
<Link to="/organization/projects">
<Link to="/organizations/$orgId/projects" params={{ orgId: currentOrg.id }}>
<SubOrgIcon className="size-[12px]" />
<span>{currentOrg.subOrganization.name}</span>
</Link>
@@ -501,8 +503,8 @@ export const Navbar = () => {
<DropdownMenuItem
onClick={async () => {
navigate({
to: "/organization/projects",
search: (prev) => ({ ...prev, subOrganization: subOrg.name })
to: "/organizations/$orgId/projects",
params: { orgId: subOrg.id }
});
await router.invalidate({ sync: true }).catch(() => null);
}}
@@ -531,21 +533,17 @@ export const Navbar = () => {
</DropdownMenu>
</>
)}
{!isOrgScope && (
{isProjectScope && (
<>
<p className="pr-3 pl-1 text-lg text-mineshaft-400/70">/</p>
{breadcrumbs ? (
<BreadcrumbContainer
className="min-w-[15rem] flex-1"
breadcrumbs={[breadcrumbs[0]] as TBreadcrumbFormat[]}
/>
) : null}
<ChevronRight size={18} className="mx-3 mt-[3px] text-mineshaft-400/70" />
<ProjectSelect />
</>
)}
</>
)}
</div>
{subscription && subscription.slug === "starter" && !subscription.has_used_trial && (
{subscription && subscription.slug === "starter" && !subscription.has_used_trial ? (
<Tooltip content="Start Free Pro Trial">
<Button
variant="plain"
@@ -566,16 +564,42 @@ export const Navbar = () => {
Free Pro Trial
</Button>
</Tooltip>
) : (
<div className="mt-0.5 mr-3 hidden rounded-sm border border-mineshaft-400 px-1 text-xs text-mineshaft-100 no-underline! opacity-50 md:inline-block">
{getPlan(subscription)}
</div>
)}
{user.superAdmin && !location.pathname.startsWith("/admin") && (
<Link
className="mr-2 flex items-center rounded-md border border-mineshaft-500 px-2.5 py-1.5 text-sm whitespace-nowrap text-mineshaft-200 hover:bg-mineshaft-600"
to="/admin"
>
<InstanceIcon className="mr-2 inline-block size-3.5" />
Server Console
</Link>
)}
{/* eslint-disable-next-line no-nested-ternary */}
{!location.pathname.startsWith("/admin") ? (
user.superAdmin ? (
<Link
className="mr-2 flex h-[34px] items-center rounded-md border border-mineshaft-500 px-2.5 py-1.5 text-sm whitespace-nowrap text-mineshaft-200 hover:bg-mineshaft-600"
to="/admin"
>
<InstanceIcon className="inline-block size-3.5" />
<span className="ml-2 hidden md:inline-block">Server Console</span>
</Link>
) : (
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Member}>
{(isAllowed) =>
isAllowed ? (
<Link
className="mr-2 flex h-[34px] items-center rounded-md border border-mineshaft-500 px-2.5 py-1.5 text-sm whitespace-nowrap text-mineshaft-200 hover:bg-mineshaft-600"
to="/organizations/$orgId/access-management"
params={{ orgId: currentOrg.id }}
search={{
selectedTab: "members",
action: "invite-members"
}}
>
<UserPlusIcon className="inline-block size-3.5" />
<span className="ml-2 hidden md:inline-block">Invite Users</span>
</Link>
) : null
}
</OrgPermissionCan>
)
) : null}
<DropdownMenu modal={false}>
<DropdownMenuTrigger>
<div className="rounded-l-md border border-r-0 border-mineshaft-500 px-2.5 py-1 hover:bg-mineshaft-600">
@@ -657,8 +681,28 @@ export const Navbar = () => {
</div>
</div>
<Link to="/personal-settings">
<DropdownMenuItem>Personal Settings</DropdownMenuItem>
<DropdownMenuItem icon={<FontAwesomeIcon icon={faUserCog} />}>
Personal Settings
</DropdownMenuItem>
</Link>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Member}>
{(isAllowed) =>
isAllowed ? (
<Link
to="/organizations/$orgId/access-management"
params={{ orgId: currentOrg.id }}
search={{
selectedTab: "members",
action: "invite-members"
}}
>
<DropdownMenuItem icon={<FontAwesomeIcon icon={faUserPlus} />}>
Invite Users
</DropdownMenuItem>
</Link>
) : null
}
</OrgPermissionCan>
<a
href="https://infisical.com/docs/documentation/getting-started/introduction"
target="_blank"
@@ -728,7 +772,11 @@ export const Navbar = () => {
</div>
<div className="mt-4">
<div className="flex space-x-3">
<Link to="/organization/billing" className="inline-flex">
<Link
to="/organizations/$orgId/billing"
params={{ orgId: currentOrg.id }}
className="inline-flex"
>
<Button
colorSchema="primary"
variant="solid"
@@ -47,8 +47,8 @@ export const NewSubOrganizationForm = ({ onClose }: ContentProps) => {
onClose();
navigate({
to: "/organization/projects",
search: (prev) => ({ ...prev, subOrganization: organization.name })
to: "/organizations/$orgId/projects",
params: { orgId: organization.id }
});
await router.invalidate({ sync: true }).catch(() => null);
};
@@ -11,7 +11,7 @@ type Props = {
};
export const OrgNavBar = ({ isHidden }: Props) => {
const { isRootOrganization } = useOrganization();
const { isRootOrganization, currentOrg } = useOrganization();
const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const);
const { pathname } = useLocation();
@@ -19,9 +19,9 @@ export const OrgNavBar = ({ isHidden }: Props) => {
const variant = isRootOrganization ? "org" : "namespace";
return (
<>
<div className="bg-mineshaft-900">
{!isHidden && (
<div className="dark hidden w-full flex-col overflow-x-hidden border-b border-mineshaft-600 bg-mineshaft-900 px-4 md:flex">
<div className="dark flex w-full flex-col overflow-x-hidden border-y border-t-org/15 border-b-org/5 bg-gradient-to-b from-org/[0.075] to-org/[0.025] px-4 pt-0.5">
<motion.div
key="menu-org-items"
initial={{ x: -150 }}
@@ -32,43 +32,47 @@ export const OrgNavBar = ({ isHidden }: Props) => {
<nav className="w-full">
<Tabs value="selected">
<TabList className="border-b-0">
<Link to="/organization/projects">
<Link to="/organizations/$orgId/projects" params={{ orgId: currentOrg.id }}>
{({ isActive }) => (
<Tab variant={variant} value={isActive ? "selected" : ""}>
Overview
</Tab>
)}
</Link>
<Link to="/organization/app-connections">
<Link
to="/organizations/$orgId/app-connections"
params={{ orgId: currentOrg.id }}
>
{({ isActive }) => (
<Tab variant={variant} value={isActive ? "selected" : ""}>
App Connections
</Tab>
)}
</Link>
<Link to="/organization/networking">
<Link to="/organizations/$orgId/networking" params={{ orgId: currentOrg.id }}>
{({ isActive }) => (
<Tab variant={variant} value={isActive ? "selected" : ""}>
Networking
</Tab>
)}
</Link>
<Link to="/organization/secret-sharing">
<Link to="/organizations/$orgId/secret-sharing" params={{ orgId: currentOrg.id }}>
{({ isActive }) => (
<Tab value={isActive ? "selected" : ""} variant={variant}>
Secret Sharing
</Tab>
)}
</Link>
<Link to="/organization/access-management">
<Link
to="/organizations/$orgId/access-management"
params={{ orgId: currentOrg.id }}
>
{({ isActive }) => (
<Tab
variant={variant}
value={
isActive ||
pathname.match(
/organization\/members|organization\/identities|organization\/groups|organization\/roles/
)
pathname.match(/organizations\/[^/]+\/(members|identities|groups|roles)/)
? "selected"
: ""
}
@@ -77,7 +81,7 @@ export const OrgNavBar = ({ isHidden }: Props) => {
</Tab>
)}
</Link>
<Link to="/organization/audit-logs">
<Link to="/organizations/$orgId/audit-logs" params={{ orgId: currentOrg.id }}>
{({ isActive }) => (
<Tab variant={variant} value={isActive ? "selected" : ""}>
Audit Logs
@@ -85,7 +89,7 @@ export const OrgNavBar = ({ isHidden }: Props) => {
)}
</Link>
{isRootOrganization && (
<Link to="/organization/billing">
<Link to="/organizations/$orgId/billing" params={{ orgId: currentOrg.id }}>
{({ isActive }) => (
<Tab variant={variant} value={isActive ? "selected" : ""}>
Usage & Billing
@@ -93,7 +97,7 @@ export const OrgNavBar = ({ isHidden }: Props) => {
)}
</Link>
)}
<Link to="/organization/settings">
<Link to="/organizations/$orgId/settings" params={{ orgId: currentOrg.id }}>
{({ isActive }) => (
<Tab variant={variant} value={isActive ? "selected" : ""}>
Settings
@@ -110,6 +114,6 @@ export const OrgNavBar = ({ isHidden }: Props) => {
isOpen={popUp?.createOrg?.isOpen}
onClose={() => handlePopUpToggle("createOrg", false)}
/>
</>
</div>
);
};

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