Merge branch 'main' into ENG-2636

This commit is contained in:
x032205
2025-05-13 13:16:59 -04:00
159 changed files with 4478 additions and 138 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+15 -3
View File
@@ -23,6 +23,10 @@ export const ROUTE_PATHS = Object.freeze({
"/_authenticate/_inject-org-details/_org-layout/organization/settings/oauth/callback"
)
},
SsoPage: setRoute(
"/organization/sso",
"/_authenticate/_inject-org-details/_org-layout/organization/sso"
),
SecretScanning: setRoute(
"/organization/secret-scanning",
"/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning"
@@ -283,9 +287,13 @@ export const ROUTE_PATHS = Object.freeze({
"/cert-manager/$projectId/ca/$caId",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caId"
),
OverviewPage: setRoute(
"/cert-manager/$projectId/overview",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview"
SubscribersPage: setRoute(
"/cert-manager/$projectId/subscribers",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers"
),
CertificatesPage: setRoute(
"/cert-manager/$projectId/certificates",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificates"
),
CertificateAuthoritiesPage: setRoute(
"/cert-manager/$projectId/certificate-authorities",
@@ -298,6 +306,10 @@ export const ROUTE_PATHS = Object.freeze({
PkiCollectionDetailsByIDPage: setRoute(
"/cert-manager/$projectId/pki-collections/$collectionId",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId"
),
PkiSubscriberDetailsByIDPage: setRoute(
"/cert-manager/$projectId/subscribers/$subscriberName",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName"
)
},
Ssh: {
@@ -9,5 +9,7 @@ export {
ProjectPermissionIdentityActions,
ProjectPermissionKmipActions,
ProjectPermissionMemberActions,
ProjectPermissionPkiSubscriberActions,
ProjectPermissionSshHostActions,
ProjectPermissionSub
} from "./types";
@@ -95,6 +95,15 @@ export enum ProjectPermissionSshHostActions {
IssueHostCert = "issue-host-cert"
}
export enum ProjectPermissionPkiSubscriberActions {
Read = "read",
Create = "create",
Edit = "edit",
Delete = "delete",
IssueCert = "issue-cert",
ListCerts = "list-certs"
}
export enum ProjectPermissionSecretRotationActions {
Read = "read",
ReadGeneratedCredentials = "read-generated-credentials",
@@ -186,6 +195,7 @@ export enum ProjectPermissionSub {
SshHostGroups = "ssh-host-groups",
PkiAlerts = "pki-alerts",
PkiCollections = "pki-collections",
PkiSubscribers = "pki-subscribers",
Kms = "kms",
Cmek = "cmek",
SecretSyncs = "secret-syncs",
@@ -220,6 +230,14 @@ export type SecretRotationSubjectFields = {
secretPath: string;
};
export type SshHostSubjectFields = {
hostname: string;
};
export type PkiSubscriberSubjectFields = {
name: string;
};
export type ProjectPermissionSet =
| [
ProjectPermissionSecretActions,
@@ -282,7 +300,20 @@ export type ProjectPermissionSet =
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates]
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificates]
| [ProjectPermissionActions, ProjectPermissionSub.SshHostGroups]
| [ProjectPermissionSshHostActions, ProjectPermissionSub.SshHosts]
| [
ProjectPermissionSshHostActions,
(
| ProjectPermissionSub.SshHosts
| (ForcedSubject<ProjectPermissionSub.SshHosts> & SshHostSubjectFields)
)
]
| [
ProjectPermissionPkiSubscriberActions,
(
| ProjectPermissionSub.PkiSubscribers
| (ForcedSubject<ProjectPermissionSub.PkiSubscribers> & PkiSubscriberSubjectFields)
)
]
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
| [ProjectPermissionSecretSyncActions, ProjectPermissionSub.SecretSyncs]
+2
View File
@@ -17,6 +17,8 @@ export {
ProjectPermissionIdentityActions,
ProjectPermissionKmipActions,
ProjectPermissionMemberActions,
ProjectPermissionPkiSubscriberActions,
ProjectPermissionSshHostActions,
ProjectPermissionSub,
useProjectPermission
} from "./ProjectPermissionContext";
+3
View File
@@ -61,6 +61,9 @@ export const initProjectHelper = async ({ projectName }: { projectName: string }
return project;
};
export const getProjectHomePage = (workspace: Workspace) => {
if (workspace.type === ProjectType.CertificateManager) {
return `/${workspace.type}/$projectId/subscribers` as const;
}
return `/${workspace.type}/$projectId/overview` as const;
};
@@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { pkiSubscriberKeys } from "../pkiSubscriber/queries";
import { workspaceKeys } from "../workspace";
import { TCertificate, TDeleteCertDTO, TRevokeCertDTO } from "./types";
@@ -42,6 +43,9 @@ export const useRevokeCert = () => {
queryClient.invalidateQueries({
queryKey: workspaceKeys.forWorkspaceCertificates(projectSlug)
});
queryClient.invalidateQueries({
queryKey: pkiSubscriberKeys.allPkiSubscriberCertificates()
});
}
});
};
+1
View File
@@ -27,6 +27,7 @@ export * from "./orgAdmin";
export * from "./organization";
export * from "./pkiAlerts";
export * from "./pkiCollections";
export * from "./pkiSubscriber";
export * from "./projectUserAdditionalPrivilege";
export * from "./rateLimit";
export * from "./roles";
@@ -0,0 +1,20 @@
export enum PkiSubscriberStatus {
ACTIVE = "active",
DISABLED = "disabled"
}
export const pkiSubscriberStatusToNameMap: { [K in PkiSubscriberStatus]: string } = {
[PkiSubscriberStatus.ACTIVE]: "Active",
[PkiSubscriberStatus.DISABLED]: "Disabled"
};
export const getPkiSubscriberStatusBadgeVariant = (status: PkiSubscriberStatus) => {
switch (status) {
case PkiSubscriberStatus.ACTIVE:
return "success";
case PkiSubscriberStatus.DISABLED:
return "danger";
default:
return "primary";
}
};
@@ -0,0 +1,7 @@
export {
useCreatePkiSubscriber,
useDeletePkiSubscriber,
useIssuePkiSubscriberCert,
useUpdatePkiSubscriber
} from "./mutations";
export { useGetPkiSubscriber, useGetPkiSubscriberCertificates } from "./queries";
@@ -0,0 +1,110 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { TCreateCertificateResponse } from "../ca/types";
import { workspaceKeys } from "../workspace/query-keys";
import { pkiSubscriberKeys } from "./queries";
import {
TCreatePkiSubscriberDTO,
TDeletePkiSubscriberDTO,
TIssuePkiSubscriberCertDTO,
TPkiSubscriber,
TUpdatePkiSubscriberDTO
} from "./types";
export const useCreatePkiSubscriber = () => {
const queryClient = useQueryClient();
return useMutation<TPkiSubscriber, object, TCreatePkiSubscriberDTO>({
mutationFn: async (body) => {
const { data: subscriber } = await apiRequest.post("/api/v1/pki/subscribers", body);
return subscriber;
},
onSuccess: ({ projectId, name }) => {
queryClient.invalidateQueries({
queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId)
});
queryClient.invalidateQueries({
queryKey: pkiSubscriberKeys.getPkiSubscriber({
subscriberName: name,
projectId
})
});
}
});
};
export const useUpdatePkiSubscriber = () => {
const queryClient = useQueryClient();
return useMutation<TPkiSubscriber, object, TUpdatePkiSubscriberDTO>({
mutationFn: async ({ subscriberName, ...body }) => {
const { data: subscriber } = await apiRequest.patch(
`/api/v1/pki/subscribers/${subscriberName}`,
body
);
return subscriber;
},
onSuccess: ({ projectId, name }) => {
queryClient.invalidateQueries({
queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId)
});
queryClient.invalidateQueries({
queryKey: pkiSubscriberKeys.getPkiSubscriber({
subscriberName: name,
projectId
})
});
}
});
};
export const useDeletePkiSubscriber = () => {
const queryClient = useQueryClient();
return useMutation<TPkiSubscriber, object, TDeletePkiSubscriberDTO>({
mutationFn: async ({ subscriberName, projectId }) => {
const { data: subscriber } = await apiRequest.delete(
`/api/v1/pki/subscribers/${subscriberName}`,
{
data: {
projectId
}
}
);
return subscriber;
},
onSuccess: ({ name, projectId }) => {
queryClient.invalidateQueries({
queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId)
});
queryClient.invalidateQueries({
queryKey: pkiSubscriberKeys.getPkiSubscriber({
subscriberName: name,
projectId
})
});
}
});
};
export const useIssuePkiSubscriberCert = () => {
const queryClient = useQueryClient();
return useMutation<TCreateCertificateResponse, object, TIssuePkiSubscriberCertDTO>({
mutationFn: async ({ subscriberName, projectId }) => {
const { data } = await apiRequest.post(
`/api/v1/pki/subscribers/${subscriberName}/issue-certificate`,
{
projectId
}
);
return data;
},
onSuccess: (_, { subscriberName, projectId }) => {
queryClient.invalidateQueries({
queryKey: pkiSubscriberKeys.forPkiSubscriberCertificates({
subscriberName,
projectId
})
});
}
});
};
@@ -0,0 +1,102 @@
import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { TCertificate } from "../certificates/types";
import { TPkiSubscriber } from "./types";
export const pkiSubscriberKeys = {
getPkiSubscriber: ({
subscriberName,
projectId
}: {
subscriberName: string;
projectId: string;
}) => [{ subscriberName, projectId }, "pki-subscriber"] as const,
allPkiSubscriberCertificates: () => ["pki-subscriber-certificates"] as const,
forPkiSubscriberCertificates: ({
subscriberName,
projectId
}: {
subscriberName: string;
projectId: string;
}) => [...pkiSubscriberKeys.allPkiSubscriberCertificates(), subscriberName, projectId] as const,
specificPkiSubscriberCertificates: ({
subscriberName,
projectId,
offset,
limit
}: {
subscriberName: string;
projectId: string;
offset: number;
limit: number;
}) =>
[
...pkiSubscriberKeys.forPkiSubscriberCertificates({ subscriberName, projectId }),
{ offset, limit, projectId }
] as const
};
export const useGetPkiSubscriber = ({
subscriberName,
projectId
}: {
subscriberName: string;
projectId: string;
}) => {
return useQuery({
queryKey: pkiSubscriberKeys.getPkiSubscriber({ subscriberName, projectId }),
queryFn: async () => {
const { data: pkiSubscriber } = await apiRequest.get<TPkiSubscriber>(
`/api/v1/pki/subscribers/${subscriberName}`,
{
params: {
projectId
}
}
);
return pkiSubscriber;
},
enabled: Boolean(subscriberName) && Boolean(projectId)
});
};
export const useGetPkiSubscriberCertificates = ({
subscriberName,
projectId,
offset,
limit
}: {
subscriberName: string;
projectId: string;
offset: number;
limit: number;
}) => {
return useQuery({
queryKey: pkiSubscriberKeys.specificPkiSubscriberCertificates({
subscriberName,
projectId,
offset,
limit
}),
queryFn: async () => {
const params = new URLSearchParams({
offset: String(offset),
limit: String(limit),
projectId
});
const {
data: { certificates, totalCount }
} = await apiRequest.get<{ certificates: TCertificate[]; totalCount: number }>(
`/api/v1/pki/subscribers/${subscriberName}/certificates`,
{
params
}
);
return { certificates, totalCount };
},
enabled: Boolean(subscriberName) && Boolean(projectId)
});
};
@@ -0,0 +1,53 @@
import { CertExtendedKeyUsage, CertKeyUsage } from "../certificates/enums";
export enum PkiSubscriberStatus {
ACTIVE = "active",
DISABLED = "disabled"
}
export type TPkiSubscriber = {
id: string;
projectId: string;
caId: string;
name: string;
commonName: string;
status: PkiSubscriberStatus;
ttl: string;
subjectAlternativeNames: string[];
keyUsages: CertKeyUsage[];
extendedKeyUsages: CertExtendedKeyUsage[];
};
export type TCreatePkiSubscriberDTO = {
projectId: string;
caId: string;
name: string;
commonName: string;
ttl: string;
subjectAlternativeNames: string[];
keyUsages: CertKeyUsage[];
extendedKeyUsages: CertExtendedKeyUsage[];
};
export type TUpdatePkiSubscriberDTO = {
subscriberName: string;
projectId: string;
caId?: string;
name?: string;
commonName?: string;
status?: PkiSubscriberStatus;
ttl?: string;
subjectAlternativeNames?: string[];
keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
};
export type TDeletePkiSubscriberDTO = {
subscriberName: string;
projectId: string;
};
export type TIssuePkiSubscriberCertDTO = {
subscriberName: string;
projectId: string;
};
+1
View File
@@ -21,6 +21,7 @@ export type TSshHost = {
hostCertTtl: string;
loginMappings: TLoginMapping[];
};
export type TCreateSshHostDTO = {
projectId: string;
hostname: string;
@@ -35,6 +35,7 @@ export {
useListWorkspaceGroups,
useListWorkspacePkiAlerts,
useListWorkspacePkiCollections,
useListWorkspacePkiSubscribers,
useListWorkspaceSshCas,
useListWorkspaceSshCertificates,
useListWorkspaceSshCertificateTemplates,
@@ -14,6 +14,7 @@ import { IntegrationAuth } from "../integrationAuth/types";
import { TIntegration } from "../integrations/types";
import { TPkiAlert } from "../pkiAlerts/types";
import { TPkiCollection } from "../pkiCollections/types";
import { TPkiSubscriber } from "../pkiSubscriber/types";
import { EncryptedSecret } from "../secrets/types";
import { TSshCertificate, TSshCertificateAuthority } from "../sshCa/types";
import { TSshCertificateTemplate } from "../sshCertificateTemplates/types";
@@ -874,6 +875,21 @@ export const useListWorkspaceSshHosts = (projectId: string) => {
});
};
export const useListWorkspacePkiSubscribers = (projectId: string) => {
return useQuery({
queryKey: workspaceKeys.getWorkspacePkiSubscribers(projectId),
queryFn: async () => {
const {
data: { subscribers }
} = await apiRequest.get<{ subscribers: TPkiSubscriber[] }>(
`/api/v2/workspace/${projectId}/pki-subscribers`
);
return subscribers;
},
enabled: Boolean(projectId)
});
};
export const useListWorkspaceSshHostGroups = (projectId: string) => {
return useQuery({
queryKey: workspaceKeys.getWorkspaceSshHostGroups(projectId),
@@ -54,6 +54,8 @@ export const workspaceKeys = {
}) => [...workspaceKeys.forWorkspaceCertificates(slug), { offset, limit }] as const,
getWorkspacePkiAlerts: (workspaceId: string) =>
[{ workspaceId }, "workspace-pki-alerts"] as const,
getWorkspacePkiSubscribers: (projectId: string) =>
[{ projectId }, "workspace-pki-subscribers"] as const,
getWorkspacePkiCollections: (workspaceId: string) =>
[{ workspaceId }, "workspace-pki-collections"] as const,
getWorkspaceCertificateTemplates: (workspaceId: string) =>
@@ -5,22 +5,6 @@ import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
export const DefaultSideBar = () => (
<Menu>
<MenuGroup title="Organization Control">
<Link to="/organization/audit-logs">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="moving-block">
Audit Logs
</MenuItem>
)}
</Link>
<Link to="/organization/billing">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="spinning-coin">
Usage & Billing
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Other">
<Link to="/organization/access-management">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="groups">
@@ -42,6 +26,29 @@ export const DefaultSideBar = () => (
</MenuItem>
)}
</Link>
<Link to="/organization/sso">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="check">
Single Sign-On (SSO)
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Other">
<Link to="/organization/audit-logs">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="moving-block">
Audit Logs
</MenuItem>
)}
</Link>
<Link to="/organization/billing">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="spinning-coin">
Usage & Billing
</MenuItem>
)}
</Link>
<Link to="/organization/settings">
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="toggle-settings">
@@ -4,6 +4,7 @@ import {
faArrowUpRightFromSquare,
faBook,
faCheck,
faCheckCircle,
faCog,
faDoorClosed,
faEnvelope,
@@ -118,6 +119,9 @@ export const MinimizedOrgSidebar = () => {
[
linkOptions({ to: "/organization/access-management" }).to,
linkOptions({ to: "/organization/app-connections" }).to,
linkOptions({ to: "/organization/billing" }).to,
linkOptions({ to: "/organization/sso" }).to,
linkOptions({ to: "/organization/gateways" }).to,
linkOptions({ to: "/organization/settings" }).to,
linkOptions({ to: "/organization/audit-logs" }).to
] as string[]
@@ -387,6 +391,13 @@ export const MinimizedOrgSidebar = () => {
Audit Logs
</DropdownMenuItem>
</Link>
<Link to="/organization/sso">
<DropdownMenuItem
icon={<FontAwesomeIcon className="w-3" icon={faCheckCircle} />}
>
SSO Settings
</DropdownMenuItem>
</Link>
<Link to="/organization/settings">
<DropdownMenuItem icon={<FontAwesomeIcon className="w-3" icon={faCog} />}>
Organization Settings
@@ -104,7 +104,23 @@ export const ProjectLayout = () => {
{isCertManager && (
<>
<Link
to={`/${ProjectType.CertificateManager}/$projectId/overview` as const}
to={
`/${ProjectType.CertificateManager}/$projectId/subscribers` as const
}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="pki-subscriber">
Subscribers
</MenuItem>
)}
</Link>
<Link
to={
`/${ProjectType.CertificateManager}/$projectId/certificates` as const
}
params={{
projectId: currentWorkspace.id
}}
@@ -15,7 +15,10 @@ export const AlertingPage = () => {
<title>{t("common.head-title", { title: "Alerting" })}</title>
</Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title="Alerting" />
<PageHeader
title="Alerting"
description="Configure alerts for expiring certificates and CAs to maintain security and compliance."
/>
<ProjectPermissionCan
renderGuardBanner
I={ProjectPermissionActions.Read}
@@ -63,7 +63,7 @@ const Page = () => {
handlePopUpClose("deleteCa");
navigate({
to: `/${ProjectType.CertificateManager}/$projectId/overview` as const,
to: `/${ProjectType.CertificateManager}/$projectId/certificates` as const,
params: {
projectId
}
@@ -15,7 +15,10 @@ export const CertificateAuthoritiesPage = () => {
<title>{t("common.head-title", { title: "Certificate Authorities" })}</title>
</Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title="Certificate Authorities" />
<PageHeader
title="Certificate Authorities"
description="Manage internal private certificate authorities for issuing and signing certificates, including root and intermediate CAs."
/>
<ProjectPermissionCan
renderGuardBanner
I={ProjectPermissionActions.Read}
@@ -32,7 +32,10 @@ export const CertificatesPage = () => {
<title>{t("common.head-title", { title: "Certificates" })}</title>
</Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title="Certificates" />
<PageHeader
title="Certificates"
description="View and track issued certificates, monitor expiration dates, and manage certificate lifecycles."
/>
{/* If both are false, the section does not render. This is to prevent duplicate banners. */}
{(canAccessCerts || canAccessPkiColl) && (
<ProjectPermissionCan
@@ -71,7 +71,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
<Table>
<THead>
<Tr>
<Th>Friendly Name</Th>
<Th>Common Name</Th>
<Th>Status</Th>
<Th>Not Before</Th>
<Th>Not After</Th>
@@ -85,7 +85,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
const { variant, label } = getCertValidUntilBadgeDetails(certificate.notAfter);
return (
<Tr className="h-10" key={`certificate-${certificate.id}`}>
<Td>{certificate.friendlyName}</Td>
<Td>{certificate.commonName}</Td>
<Td>
{certificate.status === CertStatus.REVOKED ? (
<Badge variant="danger">Revoked</Badge>
@@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { CertificatesPage } from "./CertificatesPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview"
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificates"
)({
component: CertificatesPage
});
@@ -57,7 +57,7 @@ export const PkiCollectionPage = () => {
});
handlePopUpClose("deletePkiCollection");
navigate({
to: `/${ProjectType.CertificateManager}/$projectId/overview` as const,
to: `/${ProjectType.CertificateManager}/$projectId/certificates` as const,
params: {
projectId
}
@@ -13,7 +13,7 @@ export const Route = createFileRoute(
{
label: "Certificate Collections",
link: linkOptions({
to: "/cert-manager/$projectId/overview",
to: "/cert-manager/$projectId/certificates",
params: {
projectId: params.projectId
}
@@ -0,0 +1,165 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
PageHeader,
Tooltip
} from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
import {
ProjectPermissionPkiSubscriberActions,
ProjectPermissionSub,
useWorkspace
} from "@app/context";
import { useDeletePkiSubscriber, useGetPkiSubscriber } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { usePopUp } from "@app/hooks/usePopUp";
import { PkiSubscriberModal } from "../PkiSubscribersPage/components/PkiSubscriberModal";
import { PkiSubscriberCertificatesSection, PkiSubscriberDetailsSection } from "./components";
const Page = () => {
const navigate = useNavigate();
const { currentWorkspace } = useWorkspace();
const projectId = currentWorkspace.id;
const subscriberName = useParams({
from: ROUTE_PATHS.CertManager.PkiSubscriberDetailsByIDPage.id,
select: (el) => el.subscriberName
});
const { data } = useGetPkiSubscriber({
subscriberName,
projectId
});
const { mutateAsync: deletePkiSubscriber } = useDeletePkiSubscriber();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"pkiSubscriber",
"deletePkiSubscriber"
] as const);
const onRemoveSubscriberSubmit = async (subscriberNameToDelete: string) => {
try {
if (!projectId) return;
await deletePkiSubscriber({ subscriberName: subscriberNameToDelete, projectId });
createNotification({
text: "Successfully deleted subscriber",
type: "success"
});
handlePopUpClose("deletePkiSubscriber");
navigate({
to: `/${ProjectType.CertificateManager}/$projectId/subscribers` as const,
params: {
projectId
}
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to delete subscriber",
type: "error"
});
}
};
return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && (
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title={data.name}>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options">
<Button variant="outline_bg">More</Button>
</Tooltip>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="p-1">
<ProjectPermissionCan
I={ProjectPermissionPkiSubscriberActions.Delete}
a={ProjectPermissionSub.PkiSubscribers}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
isAllowed
? "hover:!bg-red-500 hover:!text-white"
: "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={() =>
handlePopUpOpen("deletePkiSubscriber", {
subscriberName: data.name
})
}
disabled={!isAllowed}
>
Delete PKI Subscriber
</DropdownMenuItem>
)}
</ProjectPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</PageHeader>
<div className="flex">
<div className="mr-4 w-96">
<PkiSubscriberDetailsSection
subscriberName={data.name}
handlePopUpOpen={handlePopUpOpen}
/>
</div>
<div className="w-full">
<PkiSubscriberCertificatesSection subscriberName={data.name} />
</div>
</div>
</div>
)}
<PkiSubscriberModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal
isOpen={popUp.deletePkiSubscriber.isOpen}
title={`Are you sure you want to remove the PKI subscriber: ${
(popUp?.deletePkiSubscriber?.data as { name: string })?.name || ""
}?`}
onChange={(isOpen) => handlePopUpToggle("deletePkiSubscriber", isOpen)}
deleteKey="confirm"
onDeleteApproved={() =>
onRemoveSubscriberSubmit(
(popUp?.deletePkiSubscriber?.data as { subscriberName: string })?.subscriberName
)
}
/>
</div>
);
};
export const PkiSubscriberDetailsByIDPage = () => {
const { t } = useTranslation();
return (
<>
<Helmet>
<title>{t("common.head-title", { title: "PKI Subscriber" })}</title>
</Helmet>
<ProjectPermissionCan
I={ProjectPermissionPkiSubscriberActions.Read}
a={ProjectPermissionSub.PkiSubscribers}
passThrough={false}
renderGuardBanner
>
<Page />
</ProjectPermissionCan>
</>
);
};
@@ -0,0 +1,27 @@
import { usePopUp } from "@app/hooks";
import { CertificateRevocationModal } from "@app/pages/cert-manager/CertificatesPage/components/CertificateRevocationModal";
import { PkiSubscriberCertificatesTable } from "./PkiSubscriberCertificatesTable";
type Props = {
subscriberName: string;
};
export const PkiSubscriberCertificatesSection = ({ subscriberName }: Props) => {
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["revokeCertificate"] as const);
return (
<div className="h-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
<h3 className="text-lg font-semibold text-mineshaft-100">Certificates</h3>
</div>
<div className="py-4">
<PkiSubscriberCertificatesTable
subscriberName={subscriberName}
handlePopUpOpen={handlePopUpOpen}
/>
</div>
<CertificateRevocationModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
</div>
);
};
@@ -0,0 +1,176 @@
import { useState } from "react";
import { subject } from "@casl/ability";
import { faCertificate, faEllipsis, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
import { twMerge } from "tailwind-merge";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
Badge,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
EmptyState,
Pagination,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tooltip,
Tr
} from "@app/components/v2";
import {
ProjectPermissionPkiSubscriberActions,
ProjectPermissionSub,
useProjectPermission,
useWorkspace
} from "@app/context";
import { useGetPkiSubscriberCertificates } from "@app/hooks/api";
import { CertStatus } from "@app/hooks/api/certificates/enums";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
subscriberName: string;
handlePopUpOpen?: (popUpName: keyof UsePopUpState<["revokeCertificate"]>, data?: object) => void;
};
const PER_PAGE_INIT = 25;
export const PkiSubscriberCertificatesTable = ({ subscriberName, handlePopUpOpen }: Props) => {
const { currentWorkspace } = useWorkspace();
const projectId = currentWorkspace.id;
const { permission } = useProjectPermission();
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(PER_PAGE_INIT);
const { data, isPending } = useGetPkiSubscriberCertificates({
subscriberName,
projectId,
offset: (page - 1) * perPage,
limit: perPage
});
const getCertStatusBadge = (status: string, notAfter: string) => {
if (status === CertStatus.REVOKED) {
return <Badge variant="danger">Revoked</Badge>;
}
const expiryDate = new Date(notAfter);
const now = new Date();
const daysUntilExpiry = Math.floor(
(expiryDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
);
if (daysUntilExpiry < 0) {
return <Badge variant="danger">Expired</Badge>;
}
if (daysUntilExpiry < 30) {
return <Badge variant="primary">Expiring Soon</Badge>;
}
return <Badge variant="success">Valid</Badge>;
};
const canListPkiSubscriberCerts = permission.can(
ProjectPermissionPkiSubscriberActions.ListCerts,
subject(ProjectPermissionSub.PkiSubscribers, {
name: subscriberName
})
);
return (
<div>
<TableContainer>
<Table>
<THead>
<Tr>
<Th>Common Name</Th>
<Th>Status</Th>
<Th>Not Before</Th>
<Th>Not After</Th>
<Th />
</Tr>
</THead>
<TBody>
{isPending && <TableSkeleton columns={5} innerKey="pki-subscriber-certificates" />}
{!isPending &&
data?.certificates?.map((certificate) => {
return (
<Tr className="h-10" key={`certificate-${certificate.id}`}>
<Td>{certificate.commonName}</Td>
<Td>{getCertStatusBadge(certificate.status, certificate.notAfter)}</Td>
<Td>
{certificate.notBefore
? format(new Date(certificate.notBefore), "yyyy-MM-dd")
: "-"}
</Td>
<Td>
{certificate.notAfter
? format(new Date(certificate.notAfter), "yyyy-MM-dd")
: "-"}
</Td>
<Td className="flex justify-end">
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options">
<FontAwesomeIcon size="lg" icon={faEllipsis} />
</Tooltip>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<ProjectPermissionCan
I={ProjectPermissionPkiSubscriberActions.Delete}
a={ProjectPermissionSub.PkiSubscribers}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={() =>
handlePopUpOpen &&
handlePopUpOpen("revokeCertificate", {
serialNumber: certificate.serialNumber
})
}
disabled={!isAllowed}
icon={<FontAwesomeIcon icon={faTrash} />}
>
Revoke Certificate
</DropdownMenuItem>
)}
</ProjectPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</Td>
</Tr>
);
})}
</TBody>
</Table>
{!isPending && data?.totalCount !== undefined && data.totalCount >= PER_PAGE_INIT && (
<Pagination
count={data.totalCount}
page={page}
perPage={perPage}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
/>
)}
{!isPending && !data?.certificates?.length && (
<EmptyState
title={`${canListPkiSubscriberCerts ? "No certificates have been issued for this subscriber" : "You do not have permission to view this subscriber's certificates"}`}
icon={faCertificate}
/>
)}
</TableContainer>
</div>
);
};
@@ -0,0 +1,190 @@
import { useState } from "react";
import { subject } from "@casl/ability";
import { faCheck, faCopy, faPencil } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, IconButton, Modal, ModalContent, Tooltip } from "@app/components/v2";
import {
ProjectPermissionPkiSubscriberActions,
ProjectPermissionSub,
useProjectPermission,
useWorkspace
} from "@app/context";
import { useTimedReset } from "@app/hooks";
import { useGetPkiSubscriber, useIssuePkiSubscriberCert } from "@app/hooks/api";
import { pkiSubscriberStatusToNameMap } from "@app/hooks/api/pkiSubscriber/constants";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { CertificateContent } from "../../CertificatesPage/components/CertificateContent";
type Props = {
subscriberName: string;
handlePopUpOpen: (popUpName: keyof UsePopUpState<["pkiSubscriber"]>, data?: object) => void;
};
type TCertificateDetails = {
serialNumber: string;
certificate: string;
certificateChain: string;
privateKey: string;
};
export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }: Props) => {
const { currentWorkspace } = useWorkspace();
const projectId = currentWorkspace.id;
const { permission } = useProjectPermission();
const [certificateDetails, setCertificateDetails] = useState<TCertificateDetails | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset<string>({
initialState: "Copy ID to clipboard"
});
const { data: pkiSubscriber } = useGetPkiSubscriber({
subscriberName,
projectId
});
const { mutateAsync: issuePkiSubscriberCert, isPending: isIssuingCert } =
useIssuePkiSubscriberCert();
const onIssuePkiSubscriberCert = async () => {
try {
const response = await issuePkiSubscriberCert({ subscriberName, projectId });
setCertificateDetails({
serialNumber: response.serialNumber,
certificate: response.certificate,
certificateChain: response.certificateChain,
privateKey: response.privateKey
});
setIsModalOpen(true);
createNotification({
text: "Successfully issued certificate",
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to issue certificate",
type: "error"
});
}
};
const canIssuePkiSubscriberCert = permission.can(
ProjectPermissionPkiSubscriberActions.IssueCert,
subject(ProjectPermissionSub.PkiSubscribers, {
name: pkiSubscriber?.name ?? ""
})
);
return pkiSubscriber ? (
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
<h3 className="text-lg font-semibold text-mineshaft-100">PKI Subscriber Details</h3>
<ProjectPermissionCan
I={ProjectPermissionPkiSubscriberActions.Edit}
a={ProjectPermissionSub.PkiSubscribers}
>
{(isAllowed) => {
return (
<Tooltip content="Edit PKI Subscriber">
<IconButton
isDisabled={!isAllowed}
ariaLabel="edit icon"
variant="plain"
className="group relative"
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("pkiSubscriber", {
subscriberName: pkiSubscriber.name
});
}}
>
<FontAwesomeIcon icon={faPencil} />
</IconButton>
</Tooltip>
);
}}
</ProjectPermissionCan>
</div>
<div className="pt-4">
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">PKI Subscriber ID</p>
<div className="group flex align-top">
<p className="text-sm text-mineshaft-300">{pkiSubscriber.id}</p>
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
<Tooltip content={copyTextId}>
<IconButton
ariaLabel="copy icon"
variant="plain"
className="group relative ml-2"
onClick={() => {
navigator.clipboard.writeText(pkiSubscriber.id);
setCopyTextId("Copied");
}}
>
<FontAwesomeIcon icon={isCopyingId ? faCheck : faCopy} />
</IconButton>
</Tooltip>
</div>
</div>
</div>
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Name</p>
<p className="text-sm text-mineshaft-300">{pkiSubscriber.name}</p>
</div>
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Status</p>
<p className="text-sm text-mineshaft-300">
{pkiSubscriberStatusToNameMap[pkiSubscriber.status]}
</p>
</div>
<div className="mb-4">
<p className="text-sm font-semibold text-mineshaft-300">Common Name</p>
<p className="text-sm text-mineshaft-300">{pkiSubscriber.commonName}</p>
</div>
{canIssuePkiSubscriberCert && (
<Button
className="mt-4 w-full"
colorSchema="primary"
type="button"
isLoading={isIssuingCert}
onClick={() => {
onIssuePkiSubscriberCert();
}}
>
Issue Certificate
</Button>
)}
</div>
<Modal
isOpen={isModalOpen}
onOpenChange={(isOpen) => {
setIsModalOpen(isOpen);
if (!isOpen) {
setCertificateDetails(null);
}
}}
>
<ModalContent title="Certificate Details">
{certificateDetails && (
<CertificateContent
serialNumber={certificateDetails.serialNumber}
certificate={certificateDetails.certificate}
certificateChain={certificateDetails.certificateChain}
privateKey={certificateDetails.privateKey}
/>
)}
</ModalContent>
</Modal>
</div>
) : (
<div />
);
};
@@ -0,0 +1,2 @@
export { PkiSubscriberCertificatesSection } from "./PkiSubscriberCertificatesSection";
export { PkiSubscriberDetailsSection } from "./PkiSubscriberDetailsSection";
@@ -0,0 +1,25 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { PkiSubscriberDetailsByIDPage } from "./PkiSubscriberDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName"
)({
component: PkiSubscriberDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Subscribers",
link: linkOptions({
to: "/cert-manager/$projectId/subscribers",
params: {
projectId: params.projectId
}
})
}
]
};
}
});
@@ -0,0 +1,28 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { PageHeader } from "@app/components/v2";
import { PkiSubscriberSection } from "./components";
export const PkiSubscribersPage = () => {
const { t } = useTranslation();
return (
<>
<Helmet>
<title>{t("common.head-title", { title: "PKI Subscribers" })}</title>
</Helmet>
<div className="h-full bg-bunker-800">
<div className="container mx-auto flex flex-col justify-between text-white">
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader
title="Subscribers"
description="Manage subscribers that request and receive certificates, including user devices, servers, and services."
/>
<PkiSubscriberSection />
</div>
</div>
</div>
</>
);
};
@@ -0,0 +1,428 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
Button,
Checkbox,
FormControl,
Input,
Modal,
ModalContent,
Select,
SelectItem
} from "@app/components/v2";
import { useWorkspace } from "@app/context";
import {
CaStatus,
useCreatePkiSubscriber,
useGetPkiSubscriber,
useListWorkspaceCas,
useListWorkspacePkiSubscribers,
useUpdatePkiSubscriber
} from "@app/hooks/api";
import {
EXTENDED_KEY_USAGES_OPTIONS,
KEY_USAGES_OPTIONS
} from "@app/hooks/api/certificates/constants";
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/hooks/api/certificates/enums";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
popUp: UsePopUpState<["pkiSubscriber"]>;
handlePopUpToggle: (popUpName: keyof UsePopUpState<["pkiSubscriber"]>, state?: boolean) => void;
};
const schema = z
.object({
name: z.string().trim().min(1, "Name is required"),
caId: z.string().min(1, "Issuing CA is required"),
commonName: z.string().trim().min(1, "Common Name is required"),
subjectAlternativeNames: z.string(),
ttl: z.string().trim(),
keyUsages: z.object({
[CertKeyUsage.DIGITAL_SIGNATURE]: z.boolean().optional(),
[CertKeyUsage.KEY_ENCIPHERMENT]: z.boolean().optional(),
[CertKeyUsage.NON_REPUDIATION]: z.boolean().optional(),
[CertKeyUsage.DATA_ENCIPHERMENT]: z.boolean().optional(),
[CertKeyUsage.KEY_AGREEMENT]: z.boolean().optional(),
[CertKeyUsage.KEY_CERT_SIGN]: z.boolean().optional(),
[CertKeyUsage.CRL_SIGN]: z.boolean().optional(),
[CertKeyUsage.ENCIPHER_ONLY]: z.boolean().optional(),
[CertKeyUsage.DECIPHER_ONLY]: z.boolean().optional()
}),
extendedKeyUsages: z.object({
[CertExtendedKeyUsage.CLIENT_AUTH]: z.boolean().optional(),
[CertExtendedKeyUsage.CODE_SIGNING]: z.boolean().optional(),
[CertExtendedKeyUsage.EMAIL_PROTECTION]: z.boolean().optional(),
[CertExtendedKeyUsage.OCSP_SIGNING]: z.boolean().optional(),
[CertExtendedKeyUsage.SERVER_AUTH]: z.boolean().optional(),
[CertExtendedKeyUsage.TIMESTAMPING]: z.boolean().optional()
})
})
.required();
export type FormData = z.infer<typeof schema>;
export const PkiSubscriberModal = ({ popUp, handlePopUpToggle }: Props) => {
const { currentWorkspace } = useWorkspace();
const projectId = currentWorkspace.id;
const { data: subscribers } = useListWorkspacePkiSubscribers(projectId);
const { data: cas } = useListWorkspaceCas({
projectSlug: currentWorkspace?.slug ?? "",
status: CaStatus.ACTIVE
});
const { data: pkiSubscriber } = useGetPkiSubscriber({
subscriberName:
(popUp?.pkiSubscriber?.data as { subscriberName: string })?.subscriberName || "",
projectId
});
const { mutateAsync: createMutateAsync } = useCreatePkiSubscriber();
const { mutateAsync: updateMutateAsync } = useUpdatePkiSubscriber();
const {
control,
handleSubmit,
reset,
setValue,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: {
name: "",
caId: "",
commonName: "",
subjectAlternativeNames: "",
ttl: "",
keyUsages: {
[CertKeyUsage.DIGITAL_SIGNATURE]: true,
[CertKeyUsage.KEY_ENCIPHERMENT]: true
},
extendedKeyUsages: {}
}
});
useEffect(() => {
if (pkiSubscriber) {
reset({
name: pkiSubscriber.name,
caId: pkiSubscriber.caId || "",
commonName: pkiSubscriber.commonName,
subjectAlternativeNames: pkiSubscriber.subjectAlternativeNames.join(", ") || "",
ttl: pkiSubscriber.ttl || "",
keyUsages: Object.fromEntries((pkiSubscriber.keyUsages || []).map((name) => [name, true])),
extendedKeyUsages: Object.fromEntries(
(pkiSubscriber.extendedKeyUsages || []).map((name) => [name, true])
)
});
} else {
reset({
name: "",
caId: "",
commonName: "",
subjectAlternativeNames: "",
ttl: "",
keyUsages: {
[CertKeyUsage.DIGITAL_SIGNATURE]: true,
[CertKeyUsage.KEY_ENCIPHERMENT]: true
},
extendedKeyUsages: {}
});
}
}, [pkiSubscriber, reset]);
useEffect(() => {
if (cas?.length) {
setValue("caId", cas[0].id);
}
}, [cas, setValue]);
const onFormSubmit = async ({
name,
caId,
commonName,
subjectAlternativeNames,
ttl,
keyUsages,
extendedKeyUsages
}: FormData) => {
try {
if (!projectId) return;
if (!caId) {
createNotification({
text: "Please select an Issuing CA",
type: "error"
});
return;
}
// Check if there is already a different subscriber with the same name
const existingNames =
subscribers?.filter((s) => s.id !== pkiSubscriber?.id).map((s) => s.name) || [];
if (existingNames.includes(name.trim())) {
createNotification({
text: "A subscriber with this name already exists.",
type: "error"
});
return;
}
const keyUsagesList = Object.entries(keyUsages)
.filter(([, value]) => value)
.map(([key]) => key as CertKeyUsage);
const extendedKeyUsagesList = Object.entries(extendedKeyUsages)
.filter(([, value]) => value)
.map(([key]) => key as CertExtendedKeyUsage);
const subjectAlternativeNamesList = subjectAlternativeNames
.split(",")
.map((san) => san.trim())
.filter(Boolean);
if (pkiSubscriber) {
await updateMutateAsync({
subscriberName: pkiSubscriber.name,
projectId,
name,
caId,
commonName,
subjectAlternativeNames: subjectAlternativeNamesList,
ttl,
keyUsages: keyUsagesList,
extendedKeyUsages: extendedKeyUsagesList
});
} else {
await createMutateAsync({
projectId,
name,
caId,
commonName,
subjectAlternativeNames: subjectAlternativeNamesList,
ttl,
keyUsages: keyUsagesList,
extendedKeyUsages: extendedKeyUsagesList
});
}
reset();
handlePopUpToggle("pkiSubscriber", false);
createNotification({
text: `Successfully ${pkiSubscriber ? "updated" : "added"} PKI subscriber`,
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: `Failed to ${pkiSubscriber ? "update" : "add"} PKI subscriber`,
type: "error"
});
}
};
return (
<Modal
isOpen={popUp?.pkiSubscriber?.isOpen}
onOpenChange={(isOpen) => {
reset();
handlePopUpToggle("pkiSubscriber", isOpen);
}}
>
<ModalContent title={`${pkiSubscriber ? "Update" : "Add"} PKI Subscriber`}>
<form onSubmit={handleSubmit(onFormSubmit)}>
{pkiSubscriber && (
<FormControl label="Subscriber ID">
<Input value={pkiSubscriber.id} isDisabled className="bg-white/[0.07]" />
</FormControl>
)}
<Controller
control={control}
name="name"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Subscriber Name"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Input {...field} placeholder="web-service" />
</FormControl>
)}
/>
<Controller
control={control}
name="caId"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Issuing CA"
errorText={error?.message}
isError={Boolean(error)}
isRequired
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{(cas || []).map(({ id, dn }) => (
<SelectItem value={id} key={`ca-${id}`}>
{dn}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller
control={control}
name="commonName"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Common Name"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Input {...field} placeholder="web.example.com" />
</FormControl>
)}
/>
<Controller
control={control}
name="subjectAlternativeNames"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Subject Alternative Names (SANs)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="app1.example.com, app2.example.com, ..." />
</FormControl>
)}
/>
<Controller
control={control}
name="ttl"
render={({ field, fieldState: { error } }) => (
<FormControl
label="TTL"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Input {...field} placeholder="2 days, 1d, 2h, 1y, ..." />
</FormControl>
)}
/>
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="key-usages" className="data-[state=open]:border-none">
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
<div className="order-1 ml-3">Key Usage</div>
</AccordionTrigger>
<AccordionContent>
<Controller
control={control}
name="keyUsages"
render={({ field: { onChange, value }, fieldState: { error } }) => {
return (
<FormControl
label="Key Usage"
errorText={error?.message}
isError={Boolean(error)}
>
<div className="mb-7 mt-2 grid grid-cols-2 gap-2">
{KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => {
return (
<Checkbox
id={optionValue}
key={optionValue}
className="data-[state=checked]:bg-primary"
isChecked={value[optionValue]}
onCheckedChange={(state) => {
onChange({
...value,
[optionValue]: state
});
}}
>
{label}
</Checkbox>
);
})}
</div>
</FormControl>
);
}}
/>
<Controller
control={control}
name="extendedKeyUsages"
render={({ field: { onChange, value }, fieldState: { error } }) => {
return (
<FormControl
label="Extended Key Usage"
errorText={error?.message}
isError={Boolean(error)}
>
<div className="mb-7 mt-2 grid grid-cols-2 gap-2">
{EXTENDED_KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => {
return (
<Checkbox
id={optionValue}
key={optionValue}
className="data-[state=checked]:bg-primary"
isChecked={value[optionValue]}
onCheckedChange={(state) => {
onChange({
...value,
[optionValue]: state
});
}}
>
{label}
</Checkbox>
);
})}
</div>
</FormControl>
);
}}
/>
</AccordionContent>
</AccordionItem>
</Accordion>
<div className="mt-4 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{pkiSubscriber ? "Update" : "Add"}
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => handlePopUpToggle("pkiSubscriber", false)}
>
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
);
};
@@ -0,0 +1,151 @@
import { faArrowUpRightFromSquare, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2";
import {
ProjectPermissionPkiSubscriberActions,
ProjectPermissionSub,
useWorkspace
} from "@app/context";
import { useDeletePkiSubscriber, useUpdatePkiSubscriber } from "@app/hooks/api";
import { PkiSubscriberStatus } from "@app/hooks/api/pkiSubscriber/types";
import { usePopUp } from "@app/hooks/usePopUp";
import { PkiSubscriberModal } from "./PkiSubscriberModal";
import { PkiSubscribersTable } from "./PkiSubscribersTable";
export const PkiSubscriberSection = () => {
const { currentWorkspace } = useWorkspace();
const projectId = currentWorkspace.id;
const { mutateAsync: deletePkiSubscriber } = useDeletePkiSubscriber();
const { mutateAsync: updatePkiSubscriber } = useUpdatePkiSubscriber();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"pkiSubscriber",
"pkiSubscriberStatus", // enable / disable
"deletePkiSubscriber"
] as const);
const onRemovePkiSubscriberSubmit = async (subscriberName: string) => {
try {
const subscriber = await deletePkiSubscriber({ subscriberName, projectId });
createNotification({
text: `Successfully deleted PKI subscriber: ${subscriber.name}`,
type: "success"
});
handlePopUpClose("deletePkiSubscriber");
} catch (err) {
console.error(err);
createNotification({
text: "Failed to delete PKI subscriber",
type: "error"
});
}
};
const onUpdatePkiSubscriberStatus = async ({
subscriberName,
status
}: {
subscriberName: string;
status: PkiSubscriberStatus;
}) => {
try {
if (!currentWorkspace?.slug) return;
await updatePkiSubscriber({ subscriberName, projectId, status });
createNotification({
text: `Successfully ${status === PkiSubscriberStatus.ACTIVE ? "enabled" : "disabled"} subscriber`,
type: "success"
});
handlePopUpClose("pkiSubscriberStatus");
} catch (err) {
console.error(err);
createNotification({
text: `Failed to ${status === PkiSubscriberStatus.ACTIVE ? "enable" : "disable"} subscriber`,
type: "error"
});
}
};
const subscriberStatusData = popUp?.pkiSubscriberStatus?.data as {
status: PkiSubscriberStatus;
subscriberName: string;
};
const isEnabling = subscriberStatusData?.status === PkiSubscriberStatus.ACTIVE;
const subscriberName = subscriberStatusData?.subscriberName || "";
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Subscribers</p>
<div className="flex w-full justify-end">
<a
target="_blank"
rel="noopener noreferrer"
href="https://infisical.com/docs/documentation/platform/pki/subscribers"
>
<span className="flex w-max cursor-pointer items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
Documentation{" "}
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] ml-1 text-xs"
/>
</span>
</a>
<ProjectPermissionCan
I={ProjectPermissionPkiSubscriberActions.Create}
a={ProjectPermissionSub.PkiSubscribers}
>
{(isAllowed) => (
<Button
colorSchema="primary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("pkiSubscriber")}
isDisabled={!isAllowed}
className="ml-4"
>
Add Subscriber
</Button>
)}
</ProjectPermissionCan>
</div>
</div>
<PkiSubscribersTable handlePopUpOpen={handlePopUpOpen} />
<PkiSubscriberModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal
isOpen={popUp.pkiSubscriberStatus.isOpen}
title={`Are you sure you want to ${isEnabling ? "enable" : "disable"} the subscriber ${subscriberName}?`}
subTitle={
isEnabling
? "This action will allow issuing certificates for this subscriber again."
: "This action will prevent issuing certificates for this subscriber."
}
onChange={(isOpen) => handlePopUpToggle("pkiSubscriberStatus", isOpen)}
deleteKey="confirm"
buttonColorSchema={isEnabling ? "primary" : "danger"}
buttonText={isEnabling ? "Enable" : "Disable"}
onDeleteApproved={() => onUpdatePkiSubscriberStatus(subscriberStatusData)}
/>
<DeleteActionModal
isOpen={popUp.deletePkiSubscriber.isOpen}
title="Are you sure you want to remove the PKI subscriber?"
onChange={(isOpen) => handlePopUpToggle("deletePkiSubscriber", isOpen)}
deleteKey="confirm"
onDeleteApproved={() =>
onRemovePkiSubscriberSubmit(
(popUp?.deletePkiSubscriber?.data as { subscriberName: string })?.subscriberName
)
}
/>
</div>
);
};
@@ -0,0 +1,188 @@
import {
faBan,
faEllipsis,
faPencil,
faTrash,
faUserShield
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
Badge,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
EmptyState,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tooltip,
Tr
} from "@app/components/v2";
import {
ProjectPermissionPkiSubscriberActions,
ProjectPermissionSub,
useWorkspace
} from "@app/context";
import { useListWorkspacePkiSubscribers } from "@app/hooks/api";
import {
getPkiSubscriberStatusBadgeVariant,
PkiSubscriberStatus,
pkiSubscriberStatusToNameMap
} from "@app/hooks/api/pkiSubscriber/constants";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["deletePkiSubscriber", "pkiSubscriber", "pkiSubscriberStatus"]>,
data?: object
) => void;
};
export const PkiSubscribersTable = ({ handlePopUpOpen }: Props) => {
const navigate = useNavigate();
const { currentWorkspace } = useWorkspace();
const { data, isPending } = useListWorkspacePkiSubscribers(currentWorkspace?.id || "");
return (
<div>
<TableContainer>
<Table className="w-full table-fixed">
<THead>
<Tr>
<Th>Name</Th>
<Th>Status</Th>
<Th>Common Name</Th>
<Th />
</Tr>
</THead>
<TBody>
{isPending && <TableSkeleton columns={4} innerKey="pki-subscribers" />}
{!isPending &&
data &&
data.length > 0 &&
data.map((subscriber) => {
return (
<Tr
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
key={`pki-subscriber-${subscriber.id}`}
onClick={() =>
navigate({
to: `/${ProjectType.CertificateManager}/$projectId/subscribers/$subscriberName` as const,
params: {
projectId: currentWorkspace.id,
subscriberName: subscriber.name
}
})
}
>
<Td>{subscriber.name}</Td>
<Td>
<Badge variant={getPkiSubscriberStatusBadgeVariant(subscriber.status)}>
{pkiSubscriberStatusToNameMap[subscriber.status]}
</Badge>
</Td>
<Td>{subscriber.commonName}</Td>
<Td className="text-right align-middle">
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options">
<FontAwesomeIcon size="lg" icon={faEllipsis} />
</Tooltip>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<ProjectPermissionCan
I={ProjectPermissionPkiSubscriberActions.Edit}
a={ProjectPermissionSub.PkiSubscribers}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("pkiSubscriber", {
subscriberName: subscriber.name
});
}}
disabled={!isAllowed}
icon={<FontAwesomeIcon icon={faPencil} />}
>
Edit Subscriber
</DropdownMenuItem>
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionPkiSubscriberActions.Edit}
a={ProjectPermissionSub.PkiSubscribers}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("pkiSubscriberStatus", {
subscriberName: subscriber.name,
status:
subscriber.status === PkiSubscriberStatus.ACTIVE
? PkiSubscriberStatus.DISABLED
: PkiSubscriberStatus.ACTIVE
});
}}
disabled={!isAllowed}
icon={<FontAwesomeIcon icon={faBan} />}
>
{`${subscriber.status === PkiSubscriberStatus.ACTIVE ? "Disable" : "Enable"} Subscriber`}
</DropdownMenuItem>
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionPkiSubscriberActions.Delete}
a={ProjectPermissionSub.PkiSubscribers}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("deletePkiSubscriber", {
subscriberName: subscriber.name
});
}}
disabled={!isAllowed}
icon={<FontAwesomeIcon icon={faTrash} />}
>
Delete Subscriber
</DropdownMenuItem>
)}
</ProjectPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</Td>
</Tr>
);
})}
</TBody>
</Table>
{!isPending && data?.length === 0 && (
<EmptyState title="No PKI subscribers have been added" icon={faUserShield} />
)}
</TableContainer>
</div>
);
};
@@ -0,0 +1 @@
export { PkiSubscriberSection } from "./PkiSubscriberSection";
@@ -0,0 +1,9 @@
import { createFileRoute } from "@tanstack/react-router";
import { PkiSubscribersPage } from "./PkiSubscribersPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/"
)({
component: PkiSubscribersPage
});
+1 -1
View File
@@ -31,7 +31,7 @@ export const Route = createFileRoute(
{
label: project.name,
link: linkOptions({
to: "/cert-manager/$projectId/overview",
to: "/cert-manager/$projectId/subscribers",
params: { projectId: project.id }
})
}
@@ -58,6 +58,15 @@ export const OrgAdminProjects = withPermission(
await orgAdminAccessProject.mutateAsync({
projectId
});
if (type === ProjectType.CertificateManager) {
await navigate({
to: "/cert-manager/$projectId/subscribers" as const,
params: {
projectId
}
});
return;
}
await navigate({
to: `/${type}/$projectId/overview` as const,
params: {
@@ -1 +0,0 @@
export { OrgAuthTab } from "./OrgAuthTab";
@@ -4,8 +4,8 @@ import { createNotification } from "@app/components/notifications";
import { Button, DeleteActionModal } from "@app/components/v2";
import { useOrganization, useOrgPermission } from "@app/context";
import { useDeleteOrgById } from "@app/hooks/api";
import { clearSession } from "@app/hooks/api/users/queries";
import { usePopUp } from "@app/hooks/usePopUp";
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
export const OrgDeleteSection = () => {
const navigate = useNavigate();
@@ -13,9 +13,7 @@ export const OrgDeleteSection = () => {
const { membership } = useOrgPermission();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"deleteOrg"
] as const);
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["deleteOrg"] as const);
const { mutateAsync, isPending } = useDeleteOrgById();
@@ -32,9 +30,8 @@ export const OrgDeleteSection = () => {
type: "success"
});
await navigateUserToOrg(navigate);
handlePopUpClose("deleteOrg");
clearSession();
navigate({ to: "/login" });
} catch (err) {
console.error(err);
createNotification({
@@ -0,0 +1,35 @@
import { Link } from "@tanstack/react-router";
import { NoticeBannerV2 } from "@app/components/v2/NoticeBannerV2/NoticeBannerV2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { withPermission } from "@app/hoc";
import { OrgGenericAuthSection } from "./OrgGenericAuthSection";
import { OrgUserAccessTokenLimitSection } from "./OrgUserAccessTokenLimitSection";
export const OrgSecurityTab = withPermission(
() => {
return (
<>
<NoticeBannerV2
className="mx-auto mb-4"
titleClassName="text-base"
title="Single Sign-On (SSO) Settings"
>
<p className="mt-1 text-mineshaft-300">
SSO Settings have been relocated:{" "}
<Link
className="text-mineshaft-200 underline underline-offset-2"
to="/organization/sso"
>
Click here to view SSO Settings
</Link>
</p>
</NoticeBannerV2>
<OrgGenericAuthSection />
<OrgUserAccessTokenLimitSection />
</>
);
},
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Sso }
);
@@ -86,13 +86,12 @@ export const OrgUserAccessTokenLimitSection = () => {
];
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex w-full items-center justify-between">
<p className="text-xl font-semibold">User Token Expiration</p>
<p className="text-xl font-semibold">Session Length</p>
</div>
<p className="mb-4 mt-2 text-sm text-gray-400">
This defines the maximum time a user token will be valid. After this time, the user will
need to re-authenticate.
Specify the duration of each login session for users in this organization.
</p>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Settings}>
{(isAllowed) => (
@@ -0,0 +1 @@
export * from "./OrgSecurityTab";
@@ -10,9 +10,9 @@ import { ProjectType } from "@app/hooks/api/workspace/types";
import { AuditLogStreamsTab } from "../AuditLogStreamTab";
import { ImportTab } from "../ImportTab";
import { KmipTab } from "../KmipTab/OrgKmipTab";
import { OrgAuthTab } from "../OrgAuthTab";
import { OrgEncryptionTab } from "../OrgEncryptionTab";
import { OrgGeneralTab } from "../OrgGeneralTab";
import { OrgSecurityTab } from "../OrgSecurityTab";
import { OrgWorkflowIntegrationTab } from "../OrgWorkflowIntegrationTab/OrgWorkflowIntegrationTab";
export const OrgTabGroup = () => {
@@ -21,7 +21,7 @@ export const OrgTabGroup = () => {
});
const tabs = [
{ name: "General", key: "tab-org-general", component: OrgGeneralTab },
{ name: "Security", key: "tab-org-security", component: OrgAuthTab },
{ name: "Security", key: "tab-org-security", component: OrgSecurityTab },
{ name: "Encryption", key: "tab-org-encryption", component: OrgEncryptionTab },
{
name: "Workflow Integrations",
@@ -0,0 +1,21 @@
import { Helmet } from "react-helmet";
import { PageHeader } from "@app/components/v2";
import { SsoTabGroup } from "./components/SsoTabGroup";
export const SsoPage = () => {
return (
<>
<Helmet>
<title>Single Sign-On (SSO)</title>
</Helmet>
<div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl">
<PageHeader title="Single Sign-On (SSO)" />
<SsoTabGroup />
</div>
</div>
</>
);
};
@@ -0,0 +1,17 @@
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { withPermission } from "@app/hoc";
import { OrgGithubSyncSection } from "./OrgGithubSyncSection";
import { OrgScimSection } from "./OrgSCIMSection";
export const OrgProvisioningTab = withPermission(
() => {
return (
<>
<OrgScimSection />
<OrgGithubSyncSection />
</>
);
},
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Sso }
);
@@ -0,0 +1 @@
export * from "./OrgProvisioningTab";
@@ -17,16 +17,12 @@ import { LoginMethod } from "@app/hooks/api/admin/types";
import { LDAPModal } from "./LDAPModal";
import { OIDCModal } from "./OIDCModal";
import { OrgGeneralAuthSection } from "./OrgGeneralAuthSection";
import { OrgGenericAuthSection } from "./OrgGenericAuthSection";
import { OrgGithubSyncSection } from "./OrgGithubSyncSection";
import { OrgLDAPSection } from "./OrgLDAPSection";
import { OrgOIDCSection } from "./OrgOIDCSection";
import { OrgScimSection } from "./OrgSCIMSection";
import { OrgSSOSection } from "./OrgSSOSection";
import { OrgUserAccessTokenLimitSection } from "./OrgUserAccessTokenLimitSection";
import { SSOModal } from "./SSOModal";
export const OrgAuthTab = withPermission(
export const OrgSsoTab = withPermission(
() => {
const {
config: { enabledLoginMethods }
@@ -167,8 +163,6 @@ export const OrgAuthTab = withPermission(
return (
<>
<OrgGenericAuthSection />
<OrgUserAccessTokenLimitSection />
{shouldShowCreateIdentityProviderView ? (
createIdentityProviderView
) : (
@@ -183,8 +177,6 @@ export const OrgAuthTab = withPermission(
{isLdapConfigured && shouldDisplaySection(LoginMethod.LDAP) && <OrgLDAPSection />}
</>
)}
<OrgScimSection />
<OrgGithubSyncSection />
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
@@ -0,0 +1 @@
export { OrgSsoTab } from "./OrgSsoTab";
@@ -0,0 +1,37 @@
import { useState } from "react";
import { useSearch } from "@tanstack/react-router";
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
import { OrgProvisioningTab } from "../OrgProvisioningTab";
import { OrgSsoTab } from "../OrgSsoTab";
export const SsoTabGroup = () => {
const search = useSearch({
from: ROUTE_PATHS.Organization.SsoPage.id
});
const tabs = [
{ name: "General", key: "tab-sso-auth", component: OrgSsoTab },
{ name: "Provisioning", key: "tab-sso-identity", component: OrgProvisioningTab }
];
const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key);
return (
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
<TabList>
{tabs.map((tab) => (
<Tab value={tab.key} key={tab.key}>
{tab.name}
</Tab>
))}
</TabList>
{tabs.map(({ key, component: Component }) => (
<TabPanel value={key} key={`tab-panel-${key}`}>
<Component />
</TabPanel>
))}
</Tabs>
);
};
@@ -0,0 +1 @@
export { SsoTabGroup } from "./SsoTabGroup";
@@ -0,0 +1,33 @@
import { faHome } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod";
import { SsoPage } from "./SsoPage";
const SettingsPageQueryParams = z.object({
selectedTab: z.string().catch("")
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/organization/sso"
)({
component: SsoPage,
validateSearch: zodValidator(SettingsPageQueryParams),
search: {
middlewares: [stripSearchParams({ selectedTab: "" })]
},
context: () => ({
breadcrumbs: [
{
label: "Home",
icon: () => <FontAwesomeIcon icon={faHome} />,
link: linkOptions({ to: "/" })
},
{
label: "Single Sign-On (SSO)"
}
]
})
});
@@ -0,0 +1,173 @@
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Button,
FormControl,
IconButton,
Input,
Select,
SelectItem,
Tooltip
} from "@app/components/v2";
import {
PermissionConditionOperators,
ProjectPermissionSub
} from "@app/context/ProjectPermissionContext/types";
import { getConditionOperatorHelperInfo } from "./PermissionConditionHelpers";
import { TFormSchema } from "./ProjectRoleModifySection.utils";
type Props = {
position?: number;
isDisabled?: boolean;
};
export const PkiSubscriberPermissionConditions = ({ position = 0, isDisabled }: Props) => {
const {
control,
watch,
formState: { errors }
} = useFormContext<TFormSchema>();
const permissionSubject = ProjectPermissionSub.PkiSubscribers;
const items = useFieldArray({
control,
name: `permissions.${permissionSubject}.${position}.conditions`
});
return (
<div className="mt-6 border-t border-t-mineshaft-600 bg-mineshaft-800 pt-2">
<p className="mt-2 text-gray-300">Conditions</p>
<p className="text-sm text-mineshaft-400">
Conditions determine when a policy will be applied (always if no conditions are present).
</p>
<p className="mb-3 text-sm leading-4 text-mineshaft-400">
All conditions must evaluate to true for the policy to take effect.
</p>
<div className="mt-2 flex flex-col space-y-2">
{items.fields.map((el, index) => {
const condition =
(watch(`permissions.${permissionSubject}.${position}.conditions.${index}`) as {
lhs: string;
rhs: string;
operator: string;
}) || {};
return (
<div
key={el.id}
className="flex gap-2 bg-mineshaft-800 first:rounded-t-md last:rounded-b-md"
>
<div className="w-1/4">
<Controller
control={control}
name={`permissions.${permissionSubject}.${position}.conditions.${index}.lhs`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0"
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => field.onChange(e)}
className="w-full"
>
<SelectItem value="name">Name</SelectItem>
</Select>
</FormControl>
)}
/>
</div>
<div className="flex w-36 items-center space-x-2">
<Controller
control={control}
name={`permissions.${permissionSubject}.${position}.conditions.${index}.operator`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0 flex-grow"
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => field.onChange(e)}
className="w-full"
>
<SelectItem value={PermissionConditionOperators.$EQ}>Equals</SelectItem>
<SelectItem value={PermissionConditionOperators.$GLOB}>Glob</SelectItem>
<SelectItem value={PermissionConditionOperators.$IN}>In</SelectItem>
</Select>
</FormControl>
)}
/>
<Tooltip
asChild
content={getConditionOperatorHelperInfo(
condition?.operator as PermissionConditionOperators
)}
className="max-w-xs"
>
<FontAwesomeIcon icon={faInfoCircle} size="xs" className="text-gray-400" />
</Tooltip>
</div>
<div className="flex-grow">
<Controller
control={control}
name={`permissions.${permissionSubject}.${position}.conditions.${index}.rhs`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0 flex-grow"
>
<Input {...field} />
</FormControl>
)}
/>
</div>
<div>
<IconButton
ariaLabel="plus"
variant="outline_bg"
className="p-2.5"
onClick={() => items.remove(index)}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</div>
</div>
);
})}
</div>
{errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message && (
<div className="flex items-center space-x-2 py-2 text-sm text-gray-400">
<FontAwesomeIcon icon={faWarning} className="text-red" />
<span>{errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message}</span>
</div>
)}
<div>
<Button
leftIcon={<FontAwesomeIcon icon={faPlus} />}
variant="star"
size="xs"
className="mt-3"
isDisabled={isDisabled}
onClick={() =>
items.append({
lhs: "name",
operator: PermissionConditionOperators.$EQ,
rhs: ""
})
}
>
Add Condition
</Button>
</div>
</div>
);
};
@@ -17,6 +17,7 @@ import {
ProjectPermissionIdentityActions,
ProjectPermissionKmipActions,
ProjectPermissionMemberActions,
ProjectPermissionPkiSubscriberActions,
ProjectPermissionSecretActions,
ProjectPermissionSecretRotationActions,
ProjectPermissionSecretSyncActions,
@@ -131,6 +132,15 @@ const SshHostPolicyActionSchema = z.object({
[ProjectPermissionSshHostActions.IssueHostCert]: z.boolean().optional()
});
const PkiSubscriberPolicyActionSchema = z.object({
[ProjectPermissionPkiSubscriberActions.Read]: z.boolean().optional(),
[ProjectPermissionPkiSubscriberActions.Create]: z.boolean().optional(),
[ProjectPermissionPkiSubscriberActions.Edit]: z.boolean().optional(),
[ProjectPermissionPkiSubscriberActions.Delete]: z.boolean().optional(),
[ProjectPermissionPkiSubscriberActions.IssueCert]: z.boolean().optional(),
[ProjectPermissionPkiSubscriberActions.ListCerts]: z.boolean().optional()
});
const SecretRollbackPolicyActionSchema = z.object({
read: z.boolean().optional(),
create: z.boolean().optional()
@@ -230,6 +240,12 @@ export const projectRoleFormSchema = z.object({
[ProjectPermissionSub.IpAllowList]: GeneralPolicyActionSchema.array().default([]),
[ProjectPermissionSub.CertificateAuthorities]: GeneralPolicyActionSchema.array().default([]),
[ProjectPermissionSub.Certificates]: CertificatePolicyActionSchema.array().default([]),
[ProjectPermissionSub.PkiSubscribers]: PkiSubscriberPolicyActionSchema.extend({
inverted: z.boolean().optional(),
conditions: ConditionSchema
})
.array()
.default([]),
[ProjectPermissionSub.PkiAlerts]: GeneralPolicyActionSchema.array().default([]),
[ProjectPermissionSub.PkiCollections]: GeneralPolicyActionSchema.array().default([]),
[ProjectPermissionSub.CertificateTemplates]: GeneralPolicyActionSchema.array().default([]),
@@ -271,6 +287,7 @@ type TConditionalFields =
| ProjectPermissionSub.SecretFolders
| ProjectPermissionSub.SecretImports
| ProjectPermissionSub.DynamicSecrets
| ProjectPermissionSub.PkiSubscribers
| ProjectPermissionSub.SshHosts
| ProjectPermissionSub.SecretRotation
| ProjectPermissionSub.Identity;
@@ -284,7 +301,8 @@ export const isConditionalSubjects = (
subject === ProjectPermissionSub.SecretFolders ||
subject === ProjectPermissionSub.Identity ||
subject === ProjectPermissionSub.SshHosts ||
subject === ProjectPermissionSub.SecretRotation;
subject === ProjectPermissionSub.SecretRotation ||
subject === ProjectPermissionSub.PkiSubscribers;
const convertCaslConditionToFormOperator = (caslConditions: TPermissionCondition) => {
const formConditions: z.infer<typeof ConditionSchema> = [];
@@ -715,6 +733,33 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
inverted
});
}
if (subject === ProjectPermissionSub.PkiSubscribers) {
if (!formVal[subject]) formVal[subject] = [];
formVal[subject]!.push({
[ProjectPermissionPkiSubscriberActions.Edit]: action.includes(
ProjectPermissionPkiSubscriberActions.Edit
),
[ProjectPermissionPkiSubscriberActions.Delete]: action.includes(
ProjectPermissionPkiSubscriberActions.Delete
),
[ProjectPermissionPkiSubscriberActions.Create]: action.includes(
ProjectPermissionPkiSubscriberActions.Create
),
[ProjectPermissionPkiSubscriberActions.Read]: action.includes(
ProjectPermissionPkiSubscriberActions.Read
),
[ProjectPermissionPkiSubscriberActions.IssueCert]: action.includes(
ProjectPermissionPkiSubscriberActions.IssueCert
),
[ProjectPermissionPkiSubscriberActions.ListCerts]: action.includes(
ProjectPermissionPkiSubscriberActions.ListCerts
),
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
inverted
});
}
});
return formVal;
@@ -1104,6 +1149,17 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
{ label: "Remove", value: "delete" }
]
},
[ProjectPermissionSub.PkiSubscribers]: {
title: "PKI Subscribers",
actions: [
{ label: "Read", value: ProjectPermissionPkiSubscriberActions.Read },
{ label: "Create", value: ProjectPermissionPkiSubscriberActions.Create },
{ label: "Modify", value: ProjectPermissionPkiSubscriberActions.Edit },
{ label: "Remove", value: ProjectPermissionPkiSubscriberActions.Delete },
{ label: "Issue Certificate", value: ProjectPermissionPkiSubscriberActions.IssueCert },
{ label: "List Certificates", value: ProjectPermissionPkiSubscriberActions.ListCerts }
]
},
[ProjectPermissionSub.PkiCollections]: {
title: "PKI Collections",
actions: [
@@ -1233,6 +1289,7 @@ const KmsPermissionSubjects = (enabled = false) => ({
const CertificateManagerPermissionSubjects = (enabled = false) => ({
[ProjectPermissionSub.PkiCollections]: enabled,
[ProjectPermissionSub.PkiAlerts]: enabled,
[ProjectPermissionSub.PkiSubscribers]: enabled,
[ProjectPermissionSub.CertificateAuthorities]: enabled,
[ProjectPermissionSub.CertificateTemplates]: enabled,
[ProjectPermissionSub.Certificates]: enabled
@@ -23,6 +23,7 @@ import { GeneralPermissionConditions } from "./GeneralPermissionConditions";
import { GeneralPermissionPolicies } from "./GeneralPermissionPolicies";
import { IdentityManagementPermissionConditions } from "./IdentityManagementPermissionConditions";
import { PermissionEmptyState } from "./PermissionEmptyState";
import { PkiSubscriberPermissionConditions } from "./PkiSubscriberPermissionConditions";
import {
formRolePermission2API,
isConditionalSubjects,
@@ -59,6 +60,10 @@ export const renderConditionalComponents = (
return <SshHostPermissionConditions isDisabled={isDisabled} />;
}
if (subject === ProjectPermissionSub.PkiSubscribers) {
return <PkiSubscriberPermissionConditions isDisabled={isDisabled} />;
}
return <GeneralPermissionConditions isDisabled={isDisabled} type={subject} />;
}
@@ -30,7 +30,7 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { ProjectPermissionSshHostActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { fetchSshHostUserCaPublicKey, useListWorkspaceSshHosts } from "@app/hooks/api";
import { LoginMappingSource } from "@app/hooks/api/sshHost/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
@@ -221,7 +221,7 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => {
Download User CA Public Key
</DropdownMenuItem>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
I={ProjectPermissionSshHostActions.Edit}
a={ProjectPermissionSub.SshHosts}
>
{(isAllowed) => (
@@ -243,7 +243,7 @@ export const SshHostsTable = ({ handlePopUpOpen }: Props) => {
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
I={ProjectPermissionSshHostActions.Delete}
a={ProjectPermissionSub.SshHosts}
>
{(isAllowed) => (
+147 -14
View File
@@ -42,6 +42,7 @@ import { Route as adminLayoutImport } from './pages/admin/layout'
import { Route as authProviderSuccessPageRouteImport } from './pages/auth/ProviderSuccessPage/route'
import { Route as authProviderErrorPageRouteImport } from './pages/auth/ProviderErrorPage/route'
import { Route as userPersonalSettingsPageRouteImport } from './pages/user/PersonalSettingsPage/route'
import { Route as organizationSsoPageRouteImport } from './pages/organization/SsoPage/route'
import { Route as organizationSecretScanningPageRouteImport } from './pages/organization/SecretScanningPage/route'
import { Route as organizationBillingPageRouteImport } from './pages/organization/BillingPage/route'
import { Route as organizationAuditLogsPageRouteImport } from './pages/organization/AuditLogsPage/route'
@@ -118,8 +119,10 @@ import { Route as secretManagerSecretDashboardPageRouteImport } from './pages/se
import { Route as secretManagerIntegrationsSelectIntegrationAuthPageRouteImport } from './pages/secret-manager/integrations/SelectIntegrationAuthPage/route'
import { Route as secretManagerIntegrationsDetailsByIDPageRouteImport } from './pages/secret-manager/IntegrationsDetailsByIDPage/route'
import { Route as organizationAppConnectionsOauthCallbackPageRouteImport } from './pages/organization/AppConnections/OauthCallbackPage/route'
import { Route as certManagerPkiSubscriberDetailsByIDPageRouteImport } from './pages/cert-manager/PkiSubscriberDetailsByIDPage/route'
import { Route as certManagerCertAuthDetailsByIDPageRouteImport } from './pages/cert-manager/CertAuthDetailsByIDPage/route'
import { Route as secretManagerIntegrationsListPageRouteImport } from './pages/secret-manager/IntegrationsListPage/route'
import { Route as certManagerPkiSubscribersPageRouteImport } from './pages/cert-manager/PkiSubscribersPage/route'
import { Route as secretManagerIntegrationsWindmillConfigurePageRouteImport } from './pages/secret-manager/integrations/WindmillConfigurePage/route'
import { Route as secretManagerIntegrationsWindmillAuthorizePageRouteImport } from './pages/secret-manager/integrations/WindmillAuthorizePage/route'
import { Route as secretManagerIntegrationsVercelConfigurePageRouteImport } from './pages/secret-manager/integrations/VercelConfigurePage/route'
@@ -250,6 +253,10 @@ const AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdSecretManagerLa
createFileRoute(
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations',
)()
const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersImport =
createFileRoute(
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers',
)()
// Create/Update Routes
@@ -539,6 +546,12 @@ const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdRoute =
getParentRoute: () => organizationLayoutRoute,
} as any)
const organizationSsoPageRouteRoute = organizationSsoPageRouteImport.update({
id: '/sso',
path: '/sso',
getParentRoute: () => AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute,
} as any)
const organizationSecretScanningPageRouteRoute =
organizationSecretScanningPageRouteImport.update({
id: '/secret-scanning',
@@ -848,6 +861,15 @@ const secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute =
} as any,
)
const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute =
AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersImport.update(
{
id: '/subscribers',
path: '/subscribers',
getParentRoute: () => certManagerLayoutRoute,
} as any,
)
const projectAccessControlPageRouteCertManagerRoute =
projectAccessControlPageRouteCertManagerImport.update({
id: '/access-management',
@@ -949,8 +971,8 @@ const certManagerSettingsPageRouteRoute =
const certManagerCertificatesPageRouteRoute =
certManagerCertificatesPageRouteImport.update({
id: '/overview',
path: '/overview',
id: '/certificates',
path: '/certificates',
getParentRoute: () => certManagerLayoutRoute,
} as any)
@@ -1103,6 +1125,14 @@ const organizationAppConnectionsOauthCallbackPageRouteRoute =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute,
} as any)
const certManagerPkiSubscriberDetailsByIDPageRouteRoute =
certManagerPkiSubscriberDetailsByIDPageRouteImport.update({
id: '/$subscriberName',
path: '/$subscriberName',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute,
} as any)
const certManagerCertAuthDetailsByIDPageRouteRoute =
certManagerCertAuthDetailsByIDPageRouteImport.update({
id: '/ca/$caId',
@@ -1118,6 +1148,14 @@ const secretManagerIntegrationsListPageRouteRoute =
AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdSecretManagerLayoutIntegrationsRoute,
} as any)
const certManagerPkiSubscribersPageRouteRoute =
certManagerPkiSubscribersPageRouteImport.update({
id: '/',
path: '/',
getParentRoute: () =>
AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute,
} as any)
const secretManagerIntegrationsWindmillConfigurePageRouteRoute =
secretManagerIntegrationsWindmillConfigurePageRouteImport.update({
id: '/windmill/create',
@@ -2017,6 +2055,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof organizationSecretScanningPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/sso': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/sso'
path: '/sso'
fullPath: '/organization/sso'
preLoaderRoute: typeof organizationSsoPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport
}
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId': {
id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId'
path: '/cert-manager/$projectId'
@@ -2234,10 +2279,10 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof certManagerCertificateAuthoritiesPageRouteImport
parentRoute: typeof certManagerLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview': {
id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview'
path: '/overview'
fullPath: '/cert-manager/$projectId/overview'
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificates': {
id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificates'
path: '/certificates'
fullPath: '/cert-manager/$projectId/certificates'
preLoaderRoute: typeof certManagerCertificatesPageRouteImport
parentRoute: typeof certManagerLayoutImport
}
@@ -2346,6 +2391,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof projectAccessControlPageRouteCertManagerImport
parentRoute: typeof certManagerLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers': {
id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers'
path: '/subscribers'
fullPath: '/cert-manager/$projectId/subscribers'
preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersImport
parentRoute: typeof certManagerLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/integrations/azure-app-configuration/oauth2/callback': {
id: '/_authenticate/_inject-org-details/_org-layout/integrations/azure-app-configuration/oauth2/callback'
path: '/azure-app-configuration/oauth2/callback'
@@ -2437,6 +2489,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof projectAccessControlPageRouteSshImport
parentRoute: typeof sshLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/': {
id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/'
path: '/'
fullPath: '/cert-manager/$projectId/subscribers/'
preLoaderRoute: typeof certManagerPkiSubscribersPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersImport
}
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/': {
id: '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/'
path: '/'
@@ -2451,6 +2510,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof certManagerCertAuthDetailsByIDPageRouteImport
parentRoute: typeof certManagerLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName': {
id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName'
path: '/$subscriberName'
fullPath: '/cert-manager/$projectId/subscribers/$subscriberName'
preLoaderRoute: typeof certManagerPkiSubscriberDetailsByIDPageRouteImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersImport
}
'/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback': {
id: '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback'
path: '/$appConnection/oauth/callback'
@@ -3227,6 +3293,7 @@ interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren {
organizationAuditLogsPageRouteRoute: typeof organizationAuditLogsPageRouteRoute
organizationBillingPageRouteRoute: typeof organizationBillingPageRouteRoute
organizationSecretScanningPageRouteRoute: typeof organizationSecretScanningPageRouteRoute
organizationSsoPageRouteRoute: typeof organizationSsoPageRouteRoute
AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren
AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRouteWithChildren
AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren
@@ -3254,6 +3321,7 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: Authentica
organizationBillingPageRouteRoute: organizationBillingPageRouteRoute,
organizationSecretScanningPageRouteRoute:
organizationSecretScanningPageRouteRoute,
organizationSsoPageRouteRoute: organizationSsoPageRouteRoute,
AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute:
AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren,
AuthenticateInjectOrgDetailsOrgLayoutOrganizationGatewaysRoute:
@@ -3292,12 +3360,31 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteWithChildren =
AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren,
)
interface AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteChildren {
certManagerPkiSubscribersPageRouteRoute: typeof certManagerPkiSubscribersPageRouteRoute
certManagerPkiSubscriberDetailsByIDPageRouteRoute: typeof certManagerPkiSubscriberDetailsByIDPageRouteRoute
}
const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteChildren: AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteChildren =
{
certManagerPkiSubscribersPageRouteRoute:
certManagerPkiSubscribersPageRouteRoute,
certManagerPkiSubscriberDetailsByIDPageRouteRoute:
certManagerPkiSubscriberDetailsByIDPageRouteRoute,
}
const AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren =
AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute._addFileChildren(
AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteChildren,
)
interface certManagerLayoutRouteChildren {
certManagerAlertingPageRouteRoute: typeof certManagerAlertingPageRouteRoute
certManagerCertificateAuthoritiesPageRouteRoute: typeof certManagerCertificateAuthoritiesPageRouteRoute
certManagerCertificatesPageRouteRoute: typeof certManagerCertificatesPageRouteRoute
certManagerSettingsPageRouteRoute: typeof certManagerSettingsPageRouteRoute
projectAccessControlPageRouteCertManagerRoute: typeof projectAccessControlPageRouteCertManagerRoute
AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren
certManagerCertAuthDetailsByIDPageRouteRoute: typeof certManagerCertAuthDetailsByIDPageRouteRoute
projectIdentityDetailsByIDPageRouteCertManagerRoute: typeof projectIdentityDetailsByIDPageRouteCertManagerRoute
projectMemberDetailsByIDPageRouteCertManagerRoute: typeof projectMemberDetailsByIDPageRouteCertManagerRoute
@@ -3313,6 +3400,8 @@ const certManagerLayoutRouteChildren: certManagerLayoutRouteChildren = {
certManagerSettingsPageRouteRoute: certManagerSettingsPageRouteRoute,
projectAccessControlPageRouteCertManagerRoute:
projectAccessControlPageRouteCertManagerRoute,
AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRoute:
AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren,
certManagerCertAuthDetailsByIDPageRouteRoute:
certManagerCertAuthDetailsByIDPageRouteRoute,
projectIdentityDetailsByIDPageRouteCertManagerRoute:
@@ -3956,6 +4045,7 @@ export interface FileRoutesByFullPath {
'/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
'/organization/billing': typeof organizationBillingPageRouteRoute
'/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute
'/organization/sso': typeof organizationSsoPageRouteRoute
'/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren
'/kms/$projectId': typeof kmsLayoutRouteWithChildren
'/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren
@@ -3983,7 +4073,7 @@ export interface FileRoutesByFullPath {
'/organization/ssh/settings': typeof organizationSshSettingsPageRouteRoute
'/cert-manager/$projectId/alerting': typeof certManagerAlertingPageRouteRoute
'/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
'/cert-manager/$projectId/certificates': typeof certManagerCertificatesPageRouteRoute
'/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute
'/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute
'/kms/$projectId/overview': typeof kmsOverviewPageRouteRoute
@@ -3999,6 +4089,7 @@ export interface FileRoutesByFullPath {
'/ssh/$projectId/overview': typeof sshSshHostsPageRouteRoute
'/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute
'/cert-manager/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute
'/cert-manager/$projectId/subscribers': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren
'/integrations/azure-app-configuration/oauth2/callback': typeof secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute
'/integrations/azure-key-vault/oauth2/callback': typeof secretManagerIntegrationsRouteAzureKeyVaultOauthRedirectRoute
'/integrations/bitbucket/oauth2/callback': typeof secretManagerIntegrationsRouteBitbucketOauthRedirectRoute
@@ -4012,8 +4103,10 @@ export interface FileRoutesByFullPath {
'/secret-manager/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
'/secret-manager/$projectId/integrations': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteWithChildren
'/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute
'/cert-manager/$projectId/subscribers/': typeof certManagerPkiSubscribersPageRouteRoute
'/secret-manager/$projectId/integrations/': typeof secretManagerIntegrationsListPageRouteRoute
'/cert-manager/$projectId/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
'/cert-manager/$projectId/subscribers/$subscriberName': typeof certManagerPkiSubscriberDetailsByIDPageRouteRoute
'/organization/app-connections/$appConnection/oauth/callback': typeof organizationAppConnectionsOauthCallbackPageRouteRoute
'/secret-manager/$projectId/integrations/$integrationId': typeof secretManagerIntegrationsDetailsByIDPageRouteRoute
'/secret-manager/$projectId/integrations/select-integration-auth': typeof secretManagerIntegrationsSelectIntegrationAuthPageRouteRoute
@@ -4143,6 +4236,7 @@ export interface FileRoutesByTo {
'/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
'/organization/billing': typeof organizationBillingPageRouteRoute
'/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute
'/organization/sso': typeof organizationSsoPageRouteRoute
'/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren
'/kms/$projectId': typeof kmsLayoutRouteWithChildren
'/secret-manager/$projectId': typeof secretManagerLayoutRouteWithChildren
@@ -4166,7 +4260,7 @@ export interface FileRoutesByTo {
'/organization/ssh/settings': typeof organizationSshSettingsPageRouteRoute
'/cert-manager/$projectId/alerting': typeof certManagerAlertingPageRouteRoute
'/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
'/cert-manager/$projectId/certificates': typeof certManagerCertificatesPageRouteRoute
'/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute
'/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute
'/kms/$projectId/overview': typeof kmsOverviewPageRouteRoute
@@ -4194,8 +4288,10 @@ export interface FileRoutesByTo {
'/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute
'/secret-manager/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
'/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute
'/cert-manager/$projectId/subscribers': typeof certManagerPkiSubscribersPageRouteRoute
'/secret-manager/$projectId/integrations': typeof secretManagerIntegrationsListPageRouteRoute
'/cert-manager/$projectId/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
'/cert-manager/$projectId/subscribers/$subscriberName': typeof certManagerPkiSubscriberDetailsByIDPageRouteRoute
'/organization/app-connections/$appConnection/oauth/callback': typeof organizationAppConnectionsOauthCallbackPageRouteRoute
'/secret-manager/$projectId/integrations/$integrationId': typeof secretManagerIntegrationsDetailsByIDPageRouteRoute
'/secret-manager/$projectId/integrations/select-integration-auth': typeof secretManagerIntegrationsSelectIntegrationAuthPageRouteRoute
@@ -4335,6 +4431,7 @@ export interface FileRoutesById {
'/_authenticate/_inject-org-details/_org-layout/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/billing': typeof organizationBillingPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning': typeof organizationSecretScanningPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/sso': typeof organizationSsoPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutKmsProjectIdRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren
@@ -4366,7 +4463,7 @@ export interface FileRoutesById {
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout': typeof sshLayoutRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting': typeof certManagerAlertingPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview': typeof certManagerCertificatesPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificates': typeof certManagerCertificatesPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings': typeof certManagerSettingsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip': typeof kmsKmipPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/overview': typeof kmsOverviewPageRouteRoute
@@ -4382,6 +4479,7 @@ export interface FileRoutesById {
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview': typeof sshSshHostsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings': typeof sshSettingsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management': typeof projectAccessControlPageRouteCertManagerRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdCertManagerLayoutSubscribersRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/integrations/azure-app-configuration/oauth2/callback': typeof secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectRoute
'/_authenticate/_inject-org-details/_org-layout/integrations/azure-key-vault/oauth2/callback': typeof secretManagerIntegrationsRouteAzureKeyVaultOauthRedirectRoute
'/_authenticate/_inject-org-details/_org-layout/integrations/bitbucket/oauth2/callback': typeof secretManagerIntegrationsRouteBitbucketOauthRedirectRoute
@@ -4395,8 +4493,10 @@ export interface FileRoutesById {
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/access-management': typeof projectAccessControlPageRouteSecretManagerRoute
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdSecretManagerLayoutIntegrationsRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/access-management': typeof projectAccessControlPageRouteSshRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/': typeof certManagerPkiSubscribersPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/': typeof secretManagerIntegrationsListPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caId': typeof certManagerCertAuthDetailsByIDPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName': typeof certManagerPkiSubscriberDetailsByIDPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback': typeof organizationAppConnectionsOauthCallbackPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId': typeof secretManagerIntegrationsDetailsByIDPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/select-integration-auth': typeof secretManagerIntegrationsSelectIntegrationAuthPageRouteRoute
@@ -4532,6 +4632,7 @@ export interface FileRouteTypes {
| '/organization/audit-logs'
| '/organization/billing'
| '/organization/secret-scanning'
| '/organization/sso'
| '/cert-manager/$projectId'
| '/kms/$projectId'
| '/organization/app-connections'
@@ -4559,7 +4660,7 @@ export interface FileRouteTypes {
| '/organization/ssh/settings'
| '/cert-manager/$projectId/alerting'
| '/cert-manager/$projectId/certificate-authorities'
| '/cert-manager/$projectId/overview'
| '/cert-manager/$projectId/certificates'
| '/cert-manager/$projectId/settings'
| '/kms/$projectId/kmip'
| '/kms/$projectId/overview'
@@ -4575,6 +4676,7 @@ export interface FileRouteTypes {
| '/ssh/$projectId/overview'
| '/ssh/$projectId/settings'
| '/cert-manager/$projectId/access-management'
| '/cert-manager/$projectId/subscribers'
| '/integrations/azure-app-configuration/oauth2/callback'
| '/integrations/azure-key-vault/oauth2/callback'
| '/integrations/bitbucket/oauth2/callback'
@@ -4588,8 +4690,10 @@ export interface FileRouteTypes {
| '/secret-manager/$projectId/access-management'
| '/secret-manager/$projectId/integrations'
| '/ssh/$projectId/access-management'
| '/cert-manager/$projectId/subscribers/'
| '/secret-manager/$projectId/integrations/'
| '/cert-manager/$projectId/ca/$caId'
| '/cert-manager/$projectId/subscribers/$subscriberName'
| '/organization/app-connections/$appConnection/oauth/callback'
| '/secret-manager/$projectId/integrations/$integrationId'
| '/secret-manager/$projectId/integrations/select-integration-auth'
@@ -4718,6 +4822,7 @@ export interface FileRouteTypes {
| '/organization/audit-logs'
| '/organization/billing'
| '/organization/secret-scanning'
| '/organization/sso'
| '/cert-manager/$projectId'
| '/kms/$projectId'
| '/secret-manager/$projectId'
@@ -4741,7 +4846,7 @@ export interface FileRouteTypes {
| '/organization/ssh/settings'
| '/cert-manager/$projectId/alerting'
| '/cert-manager/$projectId/certificate-authorities'
| '/cert-manager/$projectId/overview'
| '/cert-manager/$projectId/certificates'
| '/cert-manager/$projectId/settings'
| '/kms/$projectId/kmip'
| '/kms/$projectId/overview'
@@ -4769,8 +4874,10 @@ export interface FileRouteTypes {
| '/kms/$projectId/access-management'
| '/secret-manager/$projectId/access-management'
| '/ssh/$projectId/access-management'
| '/cert-manager/$projectId/subscribers'
| '/secret-manager/$projectId/integrations'
| '/cert-manager/$projectId/ca/$caId'
| '/cert-manager/$projectId/subscribers/$subscriberName'
| '/organization/app-connections/$appConnection/oauth/callback'
| '/secret-manager/$projectId/integrations/$integrationId'
| '/secret-manager/$projectId/integrations/select-integration-auth'
@@ -4908,6 +5015,7 @@ export interface FileRouteTypes {
| '/_authenticate/_inject-org-details/_org-layout/organization/audit-logs'
| '/_authenticate/_inject-org-details/_org-layout/organization/billing'
| '/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning'
| '/_authenticate/_inject-org-details/_org-layout/organization/sso'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId'
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId'
| '/_authenticate/_inject-org-details/_org-layout/organization/app-connections'
@@ -4939,7 +5047,7 @@ export interface FileRouteTypes {
| '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificates'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings'
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip'
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/overview'
@@ -4955,6 +5063,7 @@ export interface FileRouteTypes {
| '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/overview'
| '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/settings'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers'
| '/_authenticate/_inject-org-details/_org-layout/integrations/azure-app-configuration/oauth2/callback'
| '/_authenticate/_inject-org-details/_org-layout/integrations/azure-key-vault/oauth2/callback'
| '/_authenticate/_inject-org-details/_org-layout/integrations/bitbucket/oauth2/callback'
@@ -4968,8 +5077,10 @@ export interface FileRouteTypes {
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/access-management'
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations'
| '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/access-management'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/'
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caId'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName'
| '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback'
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId'
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/select-integration-auth'
@@ -5304,6 +5415,7 @@ export const routeTree = rootRoute
"/_authenticate/_inject-org-details/_org-layout/organization/audit-logs",
"/_authenticate/_inject-org-details/_org-layout/organization/billing",
"/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning",
"/_authenticate/_inject-org-details/_org-layout/organization/sso",
"/_authenticate/_inject-org-details/_org-layout/organization/app-connections",
"/_authenticate/_inject-org-details/_org-layout/organization/gateways",
"/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing",
@@ -5353,6 +5465,10 @@ export const routeTree = rootRoute
"filePath": "organization/SecretScanningPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/organization/sso": {
"filePath": "organization/SsoPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization"
},
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId": {
"filePath": "",
"parent": "/_authenticate/_inject-org-details/_org-layout",
@@ -5486,9 +5602,10 @@ export const routeTree = rootRoute
"children": [
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificates",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/ca/$caId",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/identities/$identityId",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/members/$membershipId",
@@ -5550,7 +5667,7 @@ export const routeTree = rootRoute
"filePath": "cert-manager/CertificateAuthoritiesPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout"
},
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview": {
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificates": {
"filePath": "cert-manager/CertificatesPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout"
},
@@ -5614,6 +5731,14 @@ export const routeTree = rootRoute
"filePath": "project/AccessControlPage/route-cert-manager.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout"
},
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers": {
"filePath": "",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout",
"children": [
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName"
]
},
"/_authenticate/_inject-org-details/_org-layout/integrations/azure-app-configuration/oauth2/callback": {
"filePath": "secret-manager/integrations/route-azure-app-configurations-oauth-redirect.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/integrations"
@@ -5746,6 +5871,10 @@ export const routeTree = rootRoute
"filePath": "project/AccessControlPage/route-ssh.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout"
},
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/": {
"filePath": "cert-manager/PkiSubscribersPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers"
},
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/": {
"filePath": "secret-manager/IntegrationsListPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations"
@@ -5754,6 +5883,10 @@ export const routeTree = rootRoute
"filePath": "cert-manager/CertAuthDetailsByIDPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout"
},
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers/$subscriberName": {
"filePath": "cert-manager/PkiSubscriberDetailsByIDPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/subscribers"
},
"/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback": {
"filePath": "organization/AppConnections/OauthCallbackPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/organization/app-connections"
+6 -1
View File
@@ -28,6 +28,7 @@ const organizationRoutes = route("/organization", [
index("organization/SettingsPage/route.tsx"),
route("/oauth/callback", "organization/SettingsPage/OauthCallbackPage/route.tsx")
]),
route("/sso", "organization/SsoPage/route.tsx"),
route("/secret-scanning", "organization/SecretScanningPage/route.tsx"),
route("/groups/$groupId", "organization/GroupDetailsByIDPage/route.tsx"),
route("/members/$membershipId", "organization/UserDetailsByIDPage/route.tsx"),
@@ -288,7 +289,11 @@ const secretManagerIntegrationsRedirect = route("/integrations", [
const certManagerRoutes = route("/cert-manager/$projectId", [
layout("cert-manager-layout", "cert-manager/layout.tsx", [
route("/overview", "cert-manager/CertificatesPage/route.tsx"),
route("/subscribers", [
index("cert-manager/PkiSubscribersPage/route.tsx"),
route("/$subscriberName", "cert-manager/PkiSubscriberDetailsByIDPage/route.tsx")
]),
route("/certificates", "cert-manager/CertificatesPage/route.tsx"),
route("/certificate-authorities", "cert-manager/CertificateAuthoritiesPage/route.tsx"),
route("/alerting", "cert-manager/AlertingPage/route.tsx"),
route("/ca/$caId", "cert-manager/CertAuthDetailsByIDPage/route.tsx"),