feat: finished breadcrumbs for all project types except secret manager

This commit is contained in:
=
2025-01-20 22:58:59 +05:30
parent 1b1fe2a700
commit 9df8cf60ef
43 changed files with 657 additions and 422 deletions
@@ -1,47 +1,34 @@
import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { faMobile } from "@fortawesome/free-solid-svg-icons"; import { faMobile } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useQueryClient } from "@tanstack/react-query"; import { Link, Outlet, useRouterState } from "@tanstack/react-router";
import { Link, Outlet, useNavigate, useRouter } from "@tanstack/react-router";
import { Mfa } from "@app/components/auth/Mfa";
import SecurityClient from "@app/components/utilities/SecurityClient";
import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
import { useUser, useWorkspace } from "@app/context";
import { useToggle } from "@app/hooks";
import { import {
useGetAccessRequestsCount, Breadcrumb,
useGetSecretApprovalRequestCount, BreadcrumbItem,
useSelectOrganization, BreadcrumbLink,
workspaceKeys BreadcrumbList,
} from "@app/hooks/api"; BreadcrumbPage,
import { authKeys } from "@app/hooks/api/auth/queries"; BreadcrumbSeparator,
import { MfaMethod } from "@app/hooks/api/auth/types"; Menu,
MenuGroup,
MenuItem
} from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { useGetAccessRequestsCount, useGetSecretApprovalRequestCount } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/workspace/types"; import { ProjectType } from "@app/hooks/api/workspace/types";
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner"; import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
import { ProjectSelect } from "./components/ProjectSelect"; import { ProjectSelect } from "./components/ProjectSelect";
import { SidebarHeader } from "./components/SidebarHeader";
// This is a generic layout shared by all types of projects. // This is a generic layout shared by all types of projects.
// If the product layout differs significantly, create a new layout as needed. // If the product layout differs significantly, create a new layout as needed.
export const ProjectLayout = () => { export const ProjectLayout = () => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const navigate = useNavigate(); const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
const breadcrumbs = matches && "breadcrumbs" in matches ? matches.breadcrumbs : undefined;
const [shouldShowMfa, toggleShowMfa] = useToggle(false);
const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL);
const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {});
const { user } = useUser();
const { mutateAsync: selectOrganization } = useSelectOrganization();
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter();
const queryClient = useQueryClient();
const workspaceId = currentWorkspace?.id || ""; const workspaceId = currentWorkspace?.id || "";
const projectSlug = currentWorkspace?.slug || ""; const projectSlug = currentWorkspace?.slug || "";
@@ -62,40 +49,6 @@ export const ProjectLayout = () => {
const pendingRequestsCount = const pendingRequestsCount =
(secretApprovalReqCount?.open || 0) + (accessApprovalRequestCount?.pendingCount || 0); (secretApprovalReqCount?.open || 0) + (accessApprovalRequestCount?.pendingCount || 0);
const handleOrgChange = async (orgId: string) => {
queryClient.removeQueries({ queryKey: authKeys.getAuthToken });
queryClient.removeQueries({ queryKey: workspaceKeys.getAllUserWorkspace() });
const { token, isMfaEnabled, mfaMethod } = await selectOrganization({
organizationId: orgId
});
if (isMfaEnabled) {
SecurityClient.setMfaToken(token);
if (mfaMethod) {
setRequiredMfaMethod(mfaMethod);
}
toggleShowMfa.on();
setMfaSuccessCallback(() => () => handleOrgChange(orgId));
return;
}
await router.invalidate();
await navigateUserToOrg(navigate, orgId);
};
if (shouldShowMfa) {
return (
<div className="flex max-h-screen min-h-screen flex-col items-center justify-center gap-2 overflow-y-auto bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700">
<Mfa
email={user.email as string}
method={requiredMfaMethod}
successCallback={mfaSuccessCallback}
closeMfa={() => toggleShowMfa.off()}
/>
</div>
);
}
return ( return (
<> <>
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex"> <div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex">
@@ -247,6 +200,33 @@ export const ProjectLayout = () => {
</nav> </nav>
</aside> </aside>
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]"> <main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
{breadcrumbs && (
<div className="mx-auto max-w-7xl py-4 capitalize text-white">
<Breadcrumb>
<BreadcrumbList>
{breadcrumbs.map((el, index) => {
const isNotLastCrumb = index + 1 !== breadcrumbs.length;
return (
<>
{el.link && isNotLastCrumb && !("disabled" in el.link) ? (
<Link {...el.link}>
<BreadcrumbItem>
<BreadcrumbLink>{el.label}</BreadcrumbLink>
</BreadcrumbItem>
</Link>
) : (
<BreadcrumbItem>
<BreadcrumbPage>{el.label}</BreadcrumbPage>
</BreadcrumbItem>
)}
{isNotLastCrumb && <BreadcrumbSeparator />}
</>
);
})}
</BreadcrumbList>
</Breadcrumb>
</div>
)}
<Outlet /> <Outlet />
</main> </main>
</div> </div>
@@ -13,6 +13,7 @@ import {
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
PageHeader,
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes"; import { ROUTE_PATHS } from "@app/const/routes";
@@ -80,34 +81,17 @@ const Page = () => {
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && ( {data && (
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6"> <div className="mx-auto mb-6 w-full max-w-7xl">
<Button <PageHeader title={data.friendlyName}>
variant="link"
type="submit"
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
onClick={() =>
navigate({
to: `/${ProjectType.CertificateManager}/$projectId/overview` as const,
params: {
projectId
}
})
}
className="mb-4"
>
Certificate Authorities
</Button>
<div className="mb-4 flex items-center justify-between">
<p className="text-3xl font-semibold text-white">{data.friendlyName}</p>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg"> <DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400"> <div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options"> <Tooltip content="More options">
<FontAwesomeIcon size="sm" icon={faEllipsis} /> <Button variant="outline_bg">More</Button>
</Tooltip> </Tooltip>
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1"> <DropdownMenuContent align="emd" className="p-1">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Delete} I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.CertificateAuthorities} a={ProjectPermissionSub.CertificateAuthorities}
@@ -133,7 +117,7 @@ const Page = () => {
</ProjectPermissionCan> </ProjectPermissionCan>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div> </PageHeader>
<div className="flex"> <div className="flex">
<div className="mr-4 w-96"> <div className="mr-4 w-96">
<CaDetailsSection caId={caId} handlePopUpOpen={handlePopUpOpen} /> <CaDetailsSection caId={caId} handlePopUpOpen={handlePopUpOpen} />
@@ -1,9 +1,25 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { CertAuthDetailsByIDPage } from "./CertAuthDetailsByIDPage"; import { CertAuthDetailsByIDPage } from "./CertAuthDetailsByIDPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId" "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId"
)({ )({
component: CertAuthDetailsByIDPage component: CertAuthDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Certificate Authorities",
link: linkOptions({
to: "/cert-manager/$projectId/overview",
params: {
projectId: params.projectId
}
})
}
]
};
}
}); });
@@ -2,7 +2,7 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { CaTab, CertificatesTab, PkiAlertsTab } from "./components"; import { CaTab, CertificatesTab, PkiAlertsTab } from "./components";
@@ -19,11 +19,9 @@ export const CertificatesPage = () => {
<div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white"> <div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white">
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "Certificates" })}</title> <title>{t("common.head-title", { title: "Certificates" })}</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
</Helmet> </Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6"> <div className="mx-auto mb-6 w-full max-w-7xl">
<p className="mb-4 mr-4 text-3xl font-semibold text-white">Internal PKI</p> <PageHeader title="Overview" />
<Tabs defaultValue={TabSections.Certificates}> <Tabs defaultValue={TabSections.Certificates}>
<TabList> <TabList>
<Tab value={TabSections.Certificates}>Certificates</Tab> <Tab value={TabSections.Certificates}>Certificates</Tab>
@@ -1,7 +1,5 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useParams } from "@tanstack/react-router"; import { useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
@@ -14,6 +12,7 @@ import {
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
PageHeader,
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes"; import { ROUTE_PATHS } from "@app/const/routes";
@@ -74,34 +73,17 @@ export const PkiCollectionPage = () => {
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && ( {data && (
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6"> <div className="mx-auto mb-6 w-full max-w-7xl">
<Button <PageHeader title={data.name}>
variant="link"
type="submit"
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
onClick={() => {
navigate({
to: `/${ProjectType.CertificateManager}/$projectId/overview` as const,
params: {
projectId
}
});
}}
className="mb-4"
>
Certificate Collections
</Button>
<div className="mb-4 flex items-center justify-between">
<p className="text-3xl font-semibold text-white">{data.name}</p>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg"> <DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400"> <div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options"> <Tooltip content="More options">
<FontAwesomeIcon size="sm" icon={faEllipsis} /> <Button variant="outline_bg">More</Button>
</Tooltip> </Tooltip>
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1"> <DropdownMenuContent align="end" className="p-1">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Edit} I={ProjectPermissionActions.Edit}
a={ProjectPermissionSub.PkiCollections} a={ProjectPermissionSub.PkiCollections}
@@ -147,7 +129,7 @@ export const PkiCollectionPage = () => {
</ProjectPermissionCan> </ProjectPermissionCan>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div> </PageHeader>
<div className="flex"> <div className="flex">
<div className="mr-4 w-96"> <div className="mr-4 w-96">
<PkiCollectionDetailsSection <PkiCollectionDetailsSection
@@ -1,9 +1,25 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { PkiCollectionDetailsByIDPage } from "./PkiCollectionDetailsByIDPage"; import { PkiCollectionDetailsByIDPage } from "./PkiCollectionDetailsByIDPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId" "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId"
)({ )({
component: PkiCollectionDetailsByIDPage component: PkiCollectionDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Certificate Collections",
link: linkOptions({
to: "/cert-manager/$projectId/overview",
params: {
projectId: params.projectId
}
})
}
]
};
}
}); });
@@ -1,7 +1,7 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectGeneralTab } from "./components/ProjectGeneralTab"; import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
@@ -14,12 +14,9 @@ export const SettingsPage = () => {
<div className="flex h-full w-full justify-center bg-bunker-800 text-white"> <div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<Helmet> <Helmet>
<title>{t("common.head-title", { title: t("settings.project.title") })}</title> <title>{t("common.head-title", { title: t("settings.project.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Helmet> </Helmet>
<div className="w-full max-w-7xl px-6"> <div className="w-full max-w-7xl">
<div className="my-6"> <PageHeader title={t("settings.project.title")} />
<p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p>
</div>
<Tabs defaultValue={tabs[0].key}> <Tabs defaultValue={tabs[0].key}>
<TabList> <TabList>
{tabs.map((tab) => ( {tabs.map((tab) => (
@@ -5,5 +5,15 @@ import { SettingsPage } from "./SettingsPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/settings" "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/settings"
)({ )({
component: SettingsPage component: SettingsPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Settings"
}
]
};
}
}); });
+19 -8
View File
@@ -1,21 +1,16 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { workspaceKeys } from "@app/hooks/api"; import { workspaceKeys } from "@app/hooks/api";
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
import { ProjectLayout } from "@app/layouts/ProjectLayout"; import { ProjectLayout } from "@app/layouts/ProjectLayout";
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout" "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
)({ )({
component: () => ( component: ProjectLayout,
<OrganizationLayout>
<ProjectLayout />
</OrganizationLayout>
),
beforeLoad: async ({ params, context }) => { beforeLoad: async ({ params, context }) => {
await context.queryClient.ensureQueryData({ const project = await context.queryClient.ensureQueryData({
queryKey: workspaceKeys.getWorkspaceById(params.projectId), queryKey: workspaceKeys.getWorkspaceById(params.projectId),
queryFn: () => fetchWorkspaceById(params.projectId) queryFn: () => fetchWorkspaceById(params.projectId)
}); });
@@ -26,5 +21,21 @@ export const Route = createFileRoute(
}), }),
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId }) queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
}); });
return {
breadcrumbs: [
{
label: "Cert Managers",
link: linkOptions({ to: "/organization/cert-manager/overview" })
},
{
label: project.name,
link: linkOptions({
to: "/cert-manager/$projectId/overview",
params: { projectId: project.id }
})
}
]
};
} }
}); });
@@ -5,6 +5,7 @@ import { ProjectPermissionCan } from "@app/components/permissions";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { CmekTable } from "./components"; import { CmekTable } from "./components";
import { PageHeader } from "@app/components/v2";
export const OverviewPage = () => { export const OverviewPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -13,15 +14,13 @@ export const OverviewPage = () => {
<div className="h-full bg-bunker-800"> <div className="h-full bg-bunker-800">
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "KMS" })}</title> <title>{t("common.head-title", { title: "KMS" })}</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
</Helmet> </Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6"> <div className="mx-auto mb-6 w-full max-w-7xl">
<p className="mr-4 text-3xl font-semibold text-white">Key Management System</p> <PageHeader
<p className="text-md mb-4 text-bunker-300"> title="OverviewPage"
Manage keys and perform cryptographic operations. description="Manage keys and perform cryptographic operations."
</p> />
<ProjectPermissionCan <ProjectPermissionCan
passThrough={false} passThrough={false}
renderGuardBanner renderGuardBanner
@@ -1,7 +1,7 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectGeneralTab } from "./components/ProjectGeneralTab"; import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
@@ -14,12 +14,9 @@ export const SettingsPage = () => {
<div className="flex h-full w-full justify-center bg-bunker-800 text-white"> <div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<Helmet> <Helmet>
<title>{t("common.head-title", { title: t("settings.project.title") })}</title> <title>{t("common.head-title", { title: t("settings.project.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Helmet> </Helmet>
<div className="w-full max-w-7xl px-6"> <div className="w-full max-w-7xl">
<div className="my-6"> <PageHeader title={t("settings.project.title")} />
<p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p>
</div>
<Tabs defaultValue={tabs[0].key}> <Tabs defaultValue={tabs[0].key}>
<TabList> <TabList>
{tabs.map((tab) => ( {tabs.map((tab) => (
+11 -1
View File
@@ -5,5 +5,15 @@ import { SettingsPage } from "./SettingsPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/settings" "/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/settings"
)({ )({
component: SettingsPage component: SettingsPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Settings"
}
]
};
}
}); });
+19 -8
View File
@@ -1,21 +1,16 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { workspaceKeys } from "@app/hooks/api"; import { workspaceKeys } from "@app/hooks/api";
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
import { ProjectLayout } from "@app/layouts/ProjectLayout"; import { ProjectLayout } from "@app/layouts/ProjectLayout";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout" "/_authenticate/_inject-org-details/kms/$projectId/_kms-layout"
)({ )({
component: () => ( component: ProjectLayout,
<OrganizationLayout>
<ProjectLayout />
</OrganizationLayout>
),
beforeLoad: async ({ params, context }) => { beforeLoad: async ({ params, context }) => {
await context.queryClient.ensureQueryData({ const project = await context.queryClient.ensureQueryData({
queryKey: workspaceKeys.getWorkspaceById(params.projectId), queryKey: workspaceKeys.getWorkspaceById(params.projectId),
queryFn: () => fetchWorkspaceById(params.projectId) queryFn: () => fetchWorkspaceById(params.projectId)
}); });
@@ -26,5 +21,21 @@ export const Route = createFileRoute(
}), }),
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId }) queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
}); });
return {
breadcrumbs: [
{
label: "KMS",
link: linkOptions({ to: "/organization/kms/overview" })
},
{
label: project.name,
link: linkOptions({
to: "/kms/$projectId/overview",
params: { projectId: project.id }
})
}
]
};
} }
}); });
@@ -17,7 +17,7 @@ export const Route = createFileRoute(
link: linkOptions({ to: "/organization/access-management" }) link: linkOptions({ to: "/organization/access-management" })
}, },
{ {
label: "RBAC" label: "Roles"
} }
] ]
}) })
@@ -2,7 +2,7 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate, useSearch } from "@tanstack/react-router"; import { useNavigate, useSearch } from "@tanstack/react-router";
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { getProjectTitle } from "@app/helpers/project"; import { getProjectTitle } from "@app/helpers/project";
import { withProjectPermission } from "@app/hoc"; import { withProjectPermission } from "@app/hoc";
@@ -38,11 +38,11 @@ const Page = withProjectPermission(
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6"> <div className="mx-auto mb-6 w-full max-w-7xl">
<p className="mb-4 mr-4 text-3xl font-semibold text-white"> <PageHeader
{currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"} Access title="Access Control"
Control description="Manage fine-grained access for users, groups, roles, and identities within your project resources."
</p> />
<Tabs value={selectedTab} onValueChange={updateSelectedTab}> <Tabs value={selectedTab} onValueChange={updateSelectedTab}>
<TabList> <TabList>
<Tab value={ProjectAccessControlTabs.Member}>Users</Tab> <Tab value={ProjectAccessControlTabs.Member}>Users</Tab>
@@ -1,4 +1,4 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter"; import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod"; import { z } from "zod";
@@ -14,5 +14,21 @@ export const Route = createFileRoute(
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management" "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management"
)({ )({
component: AccessControlPage, component: AccessControlPage,
validateSearch: zodValidator(AccessControlPageQuerySchema) validateSearch: zodValidator(AccessControlPageQuerySchema),
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/cert-manager/$projectId/access-management",
params: {
projectId: params.projectId
}
})
}
]
};
}
}); });
@@ -1,4 +1,4 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter"; import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod"; import { z } from "zod";
@@ -14,5 +14,21 @@ export const Route = createFileRoute(
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/access-management" "/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/access-management"
)({ )({
component: AccessControlPage, component: AccessControlPage,
validateSearch: zodValidator(AccessControlPageQuerySchema) validateSearch: zodValidator(AccessControlPageQuerySchema),
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/kms/$projectId/access-management",
params: {
projectId: params.projectId
}
})
}
]
};
}
}); });
@@ -1,4 +1,4 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter"; import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod"; import { z } from "zod";
@@ -14,5 +14,21 @@ export const Route = createFileRoute(
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management" "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management"
)({ )({
component: AccessControlPage, component: AccessControlPage,
validateSearch: zodValidator(AccessControlPageQuerySchema) validateSearch: zodValidator(AccessControlPageQuerySchema),
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/secret-manager/$projectId/access-management",
params: {
projectId: params.projectId
}
})
}
]
};
}
}); });
@@ -1,4 +1,4 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter"; import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod"; import { z } from "zod";
@@ -14,5 +14,21 @@ export const Route = createFileRoute(
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management" "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management"
)({ )({
component: AccessControlPage, component: AccessControlPage,
validateSearch: zodValidator(AccessControlPageQuerySchema) validateSearch: zodValidator(AccessControlPageQuerySchema),
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/ssh/$projectId/access-management",
params: {
projectId: params.projectId
}
})
}
]
};
}
}); });
@@ -4,11 +4,11 @@ import { subject } from "@casl/ability";
import { faChevronLeft } from "@fortawesome/free-solid-svg-icons"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useParams } from "@tanstack/react-router"; import { useNavigate, useParams } from "@tanstack/react-router";
import { format } from "date-fns"; import { format, formatRelative } from "date-fns";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal, EmptyState, Spinner } from "@app/components/v2"; import { Button, DeleteActionModal, EmptyState, PageHeader, Spinner } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { getProjectTitle } from "@app/helpers/project"; import { getProjectTitle } from "@app/helpers/project";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
@@ -82,37 +82,13 @@ const Page = () => {
} }
return ( return (
<div className="container mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 p-6 text-white"> <div className="container mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 text-white">
<div className="mb-4">
<Button
variant="link"
type="submit"
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
onClick={() => {
navigate({
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
params: {
projectId: workspaceId
},
search: {
selectedTab: "identities"
}
});
}}
className="mb-4"
>
{currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"} Access
Control
</Button>
</div>
{identityMembershipDetails ? ( {identityMembershipDetails ? (
<> <>
<div className="mb-4"> <PageHeader
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> title={identityMembershipDetails?.identity?.name}
<div className="mb-4 flex items-center justify-between"> description={`Identity joined on ${identityMembershipDetails?.createdAt && formatRelative(new Date(identityMembershipDetails?.createdAt || ""), new Date())}`}
<h3 className="text-xl font-semibold text-mineshaft-100"> >
Project Identity Access
</h3>
<div> <div>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Delete} I={ProjectPermissionActions.Delete}
@@ -136,24 +112,7 @@ const Page = () => {
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
</div> </div>
</div> </PageHeader>
<div className="flex gap-12">
<div>
<span className="text-xs font-semibold text-gray-400">Name</span>
{identityMembershipDetails && (
<p className="text-lg capitalize">
{identityMembershipDetails?.identity?.name}
</p>
)}
</div>
</div>
<div className="mt-4 text-sm text-gray-400">
Joined on{" "}
{identityMembershipDetails?.createdAt &&
format(new Date(identityMembershipDetails?.createdAt || ""), "yyyy-MM-dd")}
</div>
</div>
</div>
<IdentityRoleDetailsSection <IdentityRoleDetailsSection
identityMembershipDetails={identityMembershipDetails} identityMembershipDetails={identityMembershipDetails}
isMembershipDetailsLoading={isMembershipDetailsLoading} isMembershipDetailsLoading={isMembershipDetailsLoading}
@@ -1,9 +1,28 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage"; import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId" "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId"
)({ )({
component: IdentityDetailsByIDPage component: IdentityDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/cert-manager/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Identities"
}
]
};
}
}); });
@@ -1,9 +1,28 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage"; import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/identities/$identityId" "/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/identities/$identityId"
)({ )({
component: IdentityDetailsByIDPage component: IdentityDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/kms/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Identities"
}
]
};
}
}); });
@@ -1,9 +1,28 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage"; import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/identities/$identityId" "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/identities/$identityId"
)({ )({
component: IdentityDetailsByIDPage component: IdentityDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/secret-manager/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Identities"
}
]
};
}
}); });
@@ -1,9 +1,28 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage"; import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/identities/$identityId" "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/identities/$identityId"
)({ )({
component: IdentityDetailsByIDPage component: IdentityDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/ssh/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Identities"
}
]
};
}
}); });
@@ -1,21 +1,18 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useParams } from "@tanstack/react-router"; import { useNavigate, useParams } from "@tanstack/react-router";
import { format } from "date-fns"; import { formatRelative } from "date-fns";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal, EmptyState, Spinner } from "@app/components/v2"; import { Button, DeleteActionModal, EmptyState, PageHeader, Spinner } from "@app/components/v2";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
useOrganization, useOrganization,
useWorkspace useWorkspace
} from "@app/context"; } from "@app/context";
import { getProjectTitle } from "@app/helpers/project";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useDeleteUserFromWorkspace, useGetWorkspaceUserDetails } from "@app/hooks/api"; import { useDeleteUserFromWorkspace, useGetWorkspaceUserDetails } from "@app/hooks/api";
@@ -83,32 +80,16 @@ export const Page = () => {
return ( return (
<div className="container mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 p-6 text-white"> <div className="container mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 p-6 text-white">
<div className="mb-4">
<Button
variant="link"
type="submit"
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
onClick={() => {
navigate({
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
params: {
projectId: currentWorkspace.id
}
});
}}
className="mb-4"
>
{currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"} Access
Control
</Button>
</div>
{membershipDetails ? ( {membershipDetails ? (
<> <>
<div className="mb-4"> <PageHeader
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> title={
<div className="mb-4 flex items-center justify-between"> membershipDetails.user.firstName || membershipDetails.user.lastName
<h3 className="text-xl font-semibold text-mineshaft-100">Project User Access</h3> ? `${membershipDetails.user.firstName} ${membershipDetails.user.lastName}`
<div> : "-"
}
description={`User joined on ${membershipDetails?.createdAt && formatRelative(new Date(membershipDetails?.createdAt || ""), new Date())}`}
>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Delete} I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.Member} a={ProjectPermissionSub.Member}
@@ -128,31 +109,7 @@ export const Page = () => {
</Button> </Button>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
</div> </PageHeader>
</div>
<div className="flex gap-12">
<div>
<span className="text-xs font-semibold text-gray-400">Name</span>
{membershipDetails && (
<p className="text-lg capitalize">
{membershipDetails.user.firstName || membershipDetails.user.lastName
? `${membershipDetails.user.firstName} ${membershipDetails.user.lastName}`
: "-"}
</p>
)}
</div>
<div>
<span className="text-xs font-semibold text-gray-400">Email</span>
{membershipDetails && <p className="text-lg">{membershipDetails?.user?.email}</p>}
</div>
</div>
<div className="mt-4 text-sm text-gray-400">
Joined on{" "}
{membershipDetails?.createdAt &&
format(new Date(membershipDetails?.createdAt || ""), "yyyy-MM-dd")}
</div>
</div>
</div>
<MemberRoleDetailsSection <MemberRoleDetailsSection
membershipDetails={membershipDetails} membershipDetails={membershipDetails}
isMembershipDetailsLoading={isMembershipDetailsLoading} isMembershipDetailsLoading={isMembershipDetailsLoading}
@@ -1,9 +1,28 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage"; import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/members/$membershipId" "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/members/$membershipId"
)({ )({
component: MemberDetailsByIDPage component: MemberDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/cert-manager/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Users"
}
]
};
}
}); });
@@ -1,9 +1,28 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage"; import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/members/$membershipId" "/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/members/$membershipId"
)({ )({
component: MemberDetailsByIDPage component: MemberDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/kms/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Users"
}
]
};
}
}); });
@@ -1,9 +1,28 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage"; import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/members/$membershipId" "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/members/$membershipId"
)({ )({
component: MemberDetailsByIDPage component: MemberDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/secret-manager/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Users"
}
]
};
}
}); });
@@ -1,9 +1,28 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage"; import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/members/$membershipId" "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/members/$membershipId"
)({ )({
component: MemberDetailsByIDPage component: MemberDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/ssh/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Users"
}
]
};
}
}); });
@@ -1,7 +1,5 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useParams } from "@tanstack/react-router"; import { useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
@@ -14,6 +12,7 @@ import {
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
PageHeader,
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
@@ -83,38 +82,18 @@ const Page = () => {
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && ( {data && (
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6"> <div className="mx-auto mb-6 w-full max-w-7xl">
<Button <PageHeader title={data.name}>
variant="link"
type="submit"
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
onClick={() =>
navigate({
to: `/${currentWorkspace?.type}/$projectId/access-management` as const,
params: {
projectId
},
search: {
selectedTab: ProjectAccessControlTabs.Roles
}
})
}
className="mb-4"
>
Roles
</Button>
<div className="mb-4 flex items-center justify-between">
<p className="text-3xl font-semibold text-white">{data.name}</p>
{isCustomRole && ( {isCustomRole && (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg"> <DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400"> <div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options"> <Tooltip content="More options">
<FontAwesomeIcon size="sm" icon={faEllipsis} /> <Button variant="outline_bg">More</Button>
</Tooltip> </Tooltip>
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1"> <DropdownMenuContent align="end" className="p-1">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Edit} I={ProjectPermissionActions.Edit}
a={ProjectPermissionSub.Role} a={ProjectPermissionSub.Role}
@@ -156,7 +135,7 @@ const Page = () => {
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
)} )}
</div> </PageHeader>
<div className="flex"> <div className="flex">
<div className="mr-4 w-96"> <div className="mr-4 w-96">
<RoleDetailsSection roleSlug={roleSlug} handlePopUpOpen={handlePopUpOpen} /> <RoleDetailsSection roleSlug={roleSlug} handlePopUpOpen={handlePopUpOpen} />
@@ -1,9 +1,28 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage"; import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/roles/$roleSlug" "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/roles/$roleSlug"
)({ )({
component: RoleDetailsBySlugPage component: RoleDetailsBySlugPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/cert-manager/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Roles"
}
]
};
}
}); });
@@ -1,9 +1,28 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage"; import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/roles/$roleSlug" "/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/roles/$roleSlug"
)({ )({
component: RoleDetailsBySlugPage component: RoleDetailsBySlugPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/kms/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Roles"
}
]
};
}
}); });
@@ -1,9 +1,28 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage"; import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/roles/$roleSlug" "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/roles/$roleSlug"
)({ )({
component: RoleDetailsBySlugPage component: RoleDetailsBySlugPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/secret-manager/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Roles"
}
]
};
}
}); });
@@ -1,9 +1,28 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage"; import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/roles/$roleSlug" "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/roles/$roleSlug"
)({ )({
component: RoleDetailsBySlugPage component: RoleDetailsBySlugPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/ssh/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Roles"
}
]
};
}
}); });
@@ -1,7 +1,7 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { useWorkspace } from "@app/context"; import { useWorkspace } from "@app/context";
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types"; import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
@@ -42,12 +42,9 @@ export const SettingsPage = () => {
<div className="flex h-full w-full justify-center bg-bunker-800 text-white"> <div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<Helmet> <Helmet>
<title>{t("common.head-title", { title: t("settings.project.title") })}</title> <title>{t("common.head-title", { title: t("settings.project.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Helmet> </Helmet>
<div className="w-full max-w-7xl px-6"> <div className="w-full max-w-7xl">
<div className="my-6"> <PageHeader title={t("settings.project.title")} />
<p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p>
</div>
<Tabs defaultValue={tabs[0].key}> <Tabs defaultValue={tabs[0].key}>
<TabList> <TabList>
{tabs {tabs
@@ -5,5 +5,15 @@ import { SettingsPage } from "./SettingsPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/settings" "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/settings"
)({ )({
component: SettingsPage component: SettingsPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Settings"
}
]
};
}
}); });
+19 -8
View File
@@ -1,21 +1,16 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { workspaceKeys } from "@app/hooks/api"; import { workspaceKeys } from "@app/hooks/api";
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
import { ProjectLayout } from "@app/layouts/ProjectLayout"; import { ProjectLayout } from "@app/layouts/ProjectLayout";
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout" "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
)({ )({
component: () => ( component: ProjectLayout,
<OrganizationLayout>
<ProjectLayout />
</OrganizationLayout>
),
beforeLoad: async ({ params, context }) => { beforeLoad: async ({ params, context }) => {
await context.queryClient.ensureQueryData({ const project = await context.queryClient.ensureQueryData({
queryKey: workspaceKeys.getWorkspaceById(params.projectId), queryKey: workspaceKeys.getWorkspaceById(params.projectId),
queryFn: () => fetchWorkspaceById(params.projectId) queryFn: () => fetchWorkspaceById(params.projectId)
}); });
@@ -26,5 +21,21 @@ export const Route = createFileRoute(
}), }),
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId }) queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
}); });
return {
breadcrumbs: [
{
label: "Secret Managers",
link: linkOptions({ to: "/organization/secret-manager/overview" })
},
{
label: project.name,
link: linkOptions({
to: "/secret-manager/$projectId/overview",
params: { projectId: project.id }
})
}
]
};
} }
}); });
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { SshCaSection, SshCertificatesSection } from "./components"; import { SshCaSection, SshCertificatesSection } from "./components";
@@ -19,12 +19,14 @@ export const OverviewPage = () => {
<> <>
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "Certificates" })}</title> <title>{t("common.head-title", { title: "Certificates" })}</title>
<meta property="og:image" content="/images/message.png" />
</Helmet> </Helmet>
<div className="h-full bg-bunker-800"> <div className="h-full bg-bunker-800">
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6"> <div className="mx-auto mb-6 w-full max-w-7xl">
<p className="mb-4 mr-4 text-3xl font-semibold text-white">SSH</p> <PageHeader
title="Overview"
description="Infisical SSH lets you issue SSH credentials to clients to provide short-lived, secure SSH access to infrastructure."
/>
<Tabs defaultValue={TabSections.SshCertificates}> <Tabs defaultValue={TabSections.SshCertificates}>
<TabList> <TabList>
<Tab value={TabSections.SshCertificates}>SSH Certificates</Tab> <Tab value={TabSections.SshCertificates}>SSH Certificates</Tab>
@@ -1,7 +1,7 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectGeneralTab } from "./components/ProjectGeneralTab"; import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
@@ -15,10 +15,8 @@ export const SettingsPage = () => {
<Helmet> <Helmet>
<title>{t("common.head-title", { title: t("settings.project.title") })}</title> <title>{t("common.head-title", { title: t("settings.project.title") })}</title>
</Helmet> </Helmet>
<div className="w-full max-w-7xl px-6"> <div className="w-full max-w-7xl">
<div className="my-6"> <PageHeader title={t("settings.project.title")} />
<p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p>
</div>
<Tabs defaultValue={tabs[0].key}> <Tabs defaultValue={tabs[0].key}>
<TabList> <TabList>
{tabs.map((tab) => ( {tabs.map((tab) => (
+11 -1
View File
@@ -5,5 +5,15 @@ import { SettingsPage } from "./SettingsPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/settings" "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/settings"
)({ )({
component: SettingsPage component: SettingsPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Settings"
}
]
};
}
}); });
@@ -1,6 +1,4 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useParams } from "@tanstack/react-router"; import { useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
@@ -13,6 +11,7 @@ import {
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
PageHeader,
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes"; import { ROUTE_PATHS } from "@app/const/routes";
@@ -71,34 +70,17 @@ const Page = () => {
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && ( {data && (
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6"> <div className="mx-auto mb-6 w-full max-w-7xl">
<Button <PageHeader title={data.friendlyName}>
variant="link"
type="submit"
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
onClick={() =>
navigate({
to: `/${ProjectType.SSH}/$projectId/overview`,
params: {
projectId
}
})
}
className="mb-4"
>
SSH Certificate Authorities
</Button>
<div className="mb-4 flex items-center justify-between">
<p className="text-3xl font-semibold text-white">{data.friendlyName}</p>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg"> <DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400"> <div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options"> <Tooltip content="More options">
<FontAwesomeIcon size="sm" icon={faEllipsis} /> <Button variant="outline_bg">More</Button>
</Tooltip> </Tooltip>
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1"> <DropdownMenuContent align="end" className="p-1">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Delete} I={ProjectPermissionActions.Delete}
a={ProjectPermissionSub.SshCertificateAuthorities} a={ProjectPermissionSub.SshCertificateAuthorities}
@@ -123,7 +105,7 @@ const Page = () => {
</ProjectPermissionCan> </ProjectPermissionCan>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div> </PageHeader>
<div className="flex"> <div className="flex">
<div className="mr-4 w-96"> <div className="mr-4 w-96">
<SshCaDetailsSection caId={caId} handlePopUpOpen={handlePopUpOpen} /> <SshCaDetailsSection caId={caId} handlePopUpOpen={handlePopUpOpen} />
+18 -2
View File
@@ -1,9 +1,25 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { SshCaByIDPage } from "./SshCaByIDPage"; import { SshCaByIDPage } from "./SshCaByIDPage";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId" "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId"
)({ )({
component: SshCaByIDPage component: SshCaByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "SSH Certificate Authorities",
link: linkOptions({
to: "/ssh/$projectId/overview",
params: {
projectId: params.projectId
}
})
}
]
};
}
}); });
+19 -8
View File
@@ -1,21 +1,16 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { workspaceKeys } from "@app/hooks/api"; import { workspaceKeys } from "@app/hooks/api";
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
import { ProjectLayout } from "@app/layouts/ProjectLayout"; import { ProjectLayout } from "@app/layouts/ProjectLayout";
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
export const Route = createFileRoute( export const Route = createFileRoute(
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout" "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
)({ )({
component: () => ( component: ProjectLayout,
<OrganizationLayout>
<ProjectLayout />
</OrganizationLayout>
),
beforeLoad: async ({ params, context }) => { beforeLoad: async ({ params, context }) => {
await context.queryClient.ensureQueryData({ const project = await context.queryClient.ensureQueryData({
queryKey: workspaceKeys.getWorkspaceById(params.projectId), queryKey: workspaceKeys.getWorkspaceById(params.projectId),
queryFn: () => fetchWorkspaceById(params.projectId) queryFn: () => fetchWorkspaceById(params.projectId)
}); });
@@ -26,5 +21,21 @@ export const Route = createFileRoute(
}), }),
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId }) queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
}); });
return {
breadcrumbs: [
{
label: "SSH",
link: linkOptions({ to: "/organization/ssh/overview" })
},
{
label: project.name,
link: linkOptions({
to: "/ssh/$projectId/overview",
params: { projectId: project.id }
})
}
]
};
} }
}); });