diff --git a/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx b/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx index 6f21aa788..62b94bd32 100644 --- a/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx +++ b/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx @@ -1,47 +1,34 @@ -import { useState } from "react"; import { useTranslation } from "react-i18next"; import { faMobile } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useQueryClient } from "@tanstack/react-query"; -import { Link, Outlet, useNavigate, useRouter } from "@tanstack/react-router"; +import { Link, Outlet, useRouterState } 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 { - useGetAccessRequestsCount, - useGetSecretApprovalRequestCount, - useSelectOrganization, - workspaceKeys -} from "@app/hooks/api"; -import { authKeys } from "@app/hooks/api/auth/queries"; -import { MfaMethod } from "@app/hooks/api/auth/types"; + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, + 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 { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils"; import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner"; import { ProjectSelect } from "./components/ProjectSelect"; -import { SidebarHeader } from "./components/SidebarHeader"; // This is a generic layout shared by all types of projects. // If the product layout differs significantly, create a new layout as needed. export const ProjectLayout = () => { const { currentWorkspace } = useWorkspace(); - const navigate = useNavigate(); - - const [shouldShowMfa, toggleShowMfa] = useToggle(false); - const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL); - const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {}); - - const { user } = useUser(); - - const { mutateAsync: selectOrganization } = useSelectOrganization(); + const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context }); + const breadcrumbs = matches && "breadcrumbs" in matches ? matches.breadcrumbs : undefined; const { t } = useTranslation(); - const router = useRouter(); - const queryClient = useQueryClient(); const workspaceId = currentWorkspace?.id || ""; const projectSlug = currentWorkspace?.slug || ""; @@ -62,40 +49,6 @@ export const ProjectLayout = () => { const pendingRequestsCount = (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 ( -
- toggleShowMfa.off()} - /> -
- ); - } - return ( <>
@@ -247,6 +200,33 @@ export const ProjectLayout = () => {
+ {breadcrumbs && ( +
+ + + {breadcrumbs.map((el, index) => { + const isNotLastCrumb = index + 1 !== breadcrumbs.length; + return ( + <> + {el.link && isNotLastCrumb && !("disabled" in el.link) ? ( + + + {el.label} + + + ) : ( + + {el.label} + + )} + {isNotLastCrumb && } + + ); + })} + + +
+ )}
diff --git a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx index 3c753fb52..ba3bc311a 100644 --- a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx +++ b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx @@ -13,6 +13,7 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, + PageHeader, Tooltip } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; @@ -80,34 +81,17 @@ const Page = () => { return (
{data && ( -
- -
-

{data.friendlyName}

+
+
- +
- + {
-
+
diff --git a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/route.tsx b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/route.tsx index 112d0c4c1..ac3342f57 100644 --- a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/route.tsx +++ b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/route.tsx @@ -1,9 +1,25 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { CertAuthDetailsByIDPage } from "./CertAuthDetailsByIDPage"; export const Route = createFileRoute( "/_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 + } + }) + } + ] + }; + } }); diff --git a/frontend/src/pages/cert-manager/CertificatesPage/CertificatesPage.tsx b/frontend/src/pages/cert-manager/CertificatesPage/CertificatesPage.tsx index e18782cdc..0d212e797 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/CertificatesPage.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/CertificatesPage.tsx @@ -2,7 +2,7 @@ import { Helmet } from "react-helmet"; import { useTranslation } from "react-i18next"; 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 { CaTab, CertificatesTab, PkiAlertsTab } from "./components"; @@ -19,11 +19,9 @@ export const CertificatesPage = () => {
{t("common.head-title", { title: "Certificates" })} - - -
-

Internal PKI

+
+ Certificates diff --git a/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/PkiCollectionDetailsByIDPage.tsx b/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/PkiCollectionDetailsByIDPage.tsx index c0f7b6e97..5cd8bf7c6 100644 --- a/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/PkiCollectionDetailsByIDPage.tsx +++ b/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/PkiCollectionDetailsByIDPage.tsx @@ -1,7 +1,5 @@ import { Helmet } from "react-helmet"; 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 { twMerge } from "tailwind-merge"; @@ -14,6 +12,7 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, + PageHeader, Tooltip } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; @@ -74,34 +73,17 @@ export const PkiCollectionPage = () => { return (
{data && ( -
- -
-

{data.name}

+
+
- +
- + {
-
+
{ + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Certificate Collections", + link: linkOptions({ + to: "/cert-manager/$projectId/overview", + params: { + projectId: params.projectId + } + }) + } + ] + }; + } }); diff --git a/frontend/src/pages/cert-manager/SettingsPage/SettingsPage.tsx b/frontend/src/pages/cert-manager/SettingsPage/SettingsPage.tsx index 99ec9033f..dce31f01b 100644 --- a/frontend/src/pages/cert-manager/SettingsPage/SettingsPage.tsx +++ b/frontend/src/pages/cert-manager/SettingsPage/SettingsPage.tsx @@ -1,7 +1,7 @@ import { Helmet } from "react-helmet"; 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"; @@ -14,12 +14,9 @@ export const SettingsPage = () => {
{t("common.head-title", { title: t("settings.project.title") })} - -
-
-

{t("settings.project.title")}

-
+
+ {tabs.map((tab) => ( diff --git a/frontend/src/pages/cert-manager/SettingsPage/route.tsx b/frontend/src/pages/cert-manager/SettingsPage/route.tsx index bf769e482..366c1cbf9 100644 --- a/frontend/src/pages/cert-manager/SettingsPage/route.tsx +++ b/frontend/src/pages/cert-manager/SettingsPage/route.tsx @@ -5,5 +5,15 @@ import { SettingsPage } from "./SettingsPage"; export const Route = createFileRoute( "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/settings" )({ - component: SettingsPage + component: SettingsPage, + beforeLoad: ({ context }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Settings" + } + ] + }; + } }); diff --git a/frontend/src/pages/cert-manager/layout.tsx b/frontend/src/pages/cert-manager/layout.tsx index a9a68de25..1d33e866a 100644 --- a/frontend/src/pages/cert-manager/layout.tsx +++ b/frontend/src/pages/cert-manager/layout.tsx @@ -1,21 +1,16 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { workspaceKeys } from "@app/hooks/api"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { ProjectLayout } from "@app/layouts/ProjectLayout"; -import { OrganizationLayout } from "@app/layouts/OrganizationLayout"; export const Route = createFileRoute( "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout" )({ - component: () => ( - - - - ), + component: ProjectLayout, beforeLoad: async ({ params, context }) => { - await context.queryClient.ensureQueryData({ + const project = await context.queryClient.ensureQueryData({ queryKey: workspaceKeys.getWorkspaceById(params.projectId), queryFn: () => fetchWorkspaceById(params.projectId) }); @@ -26,5 +21,21 @@ export const Route = createFileRoute( }), 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 } + }) + } + ] + }; } }); diff --git a/frontend/src/pages/kms/OverviewPage/OverviewPage.tsx b/frontend/src/pages/kms/OverviewPage/OverviewPage.tsx index 1d36210e0..57f756916 100644 --- a/frontend/src/pages/kms/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/kms/OverviewPage/OverviewPage.tsx @@ -5,6 +5,7 @@ import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { CmekTable } from "./components"; +import { PageHeader } from "@app/components/v2"; export const OverviewPage = () => { const { t } = useTranslation(); @@ -13,15 +14,13 @@ export const OverviewPage = () => {
{t("common.head-title", { title: "KMS" })} - -
-
-

Key Management System

-

- Manage keys and perform cryptographic operations. -

+
+ {
{t("common.head-title", { title: t("settings.project.title") })} - -
-
-

{t("settings.project.title")}

-
+
+ {tabs.map((tab) => ( diff --git a/frontend/src/pages/kms/SettingsPage/route.tsx b/frontend/src/pages/kms/SettingsPage/route.tsx index c5578c700..37f0525b6 100644 --- a/frontend/src/pages/kms/SettingsPage/route.tsx +++ b/frontend/src/pages/kms/SettingsPage/route.tsx @@ -5,5 +5,15 @@ import { SettingsPage } from "./SettingsPage"; export const Route = createFileRoute( "/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/settings" )({ - component: SettingsPage + component: SettingsPage, + beforeLoad: ({ context }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Settings" + } + ] + }; + } }); diff --git a/frontend/src/pages/kms/layout.tsx b/frontend/src/pages/kms/layout.tsx index 5a78be252..a9874fb56 100644 --- a/frontend/src/pages/kms/layout.tsx +++ b/frontend/src/pages/kms/layout.tsx @@ -1,21 +1,16 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { workspaceKeys } from "@app/hooks/api"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; -import { OrganizationLayout } from "@app/layouts/OrganizationLayout"; import { ProjectLayout } from "@app/layouts/ProjectLayout"; export const Route = createFileRoute( "/_authenticate/_inject-org-details/kms/$projectId/_kms-layout" )({ - component: () => ( - - - - ), + component: ProjectLayout, beforeLoad: async ({ params, context }) => { - await context.queryClient.ensureQueryData({ + const project = await context.queryClient.ensureQueryData({ queryKey: workspaceKeys.getWorkspaceById(params.projectId), queryFn: () => fetchWorkspaceById(params.projectId) }); @@ -26,5 +21,21 @@ export const Route = createFileRoute( }), 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 } + }) + } + ] + }; } }); diff --git a/frontend/src/pages/organization/RoleByIDPage/route.tsx b/frontend/src/pages/organization/RoleByIDPage/route.tsx index 9ff1f4f4d..250478f19 100644 --- a/frontend/src/pages/organization/RoleByIDPage/route.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/route.tsx @@ -17,7 +17,7 @@ export const Route = createFileRoute( link: linkOptions({ to: "/organization/access-management" }) }, { - label: "RBAC" + label: "Roles" } ] }) diff --git a/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx b/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx index 2fbc9e67a..d95ccddf0 100644 --- a/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx +++ b/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx @@ -2,7 +2,7 @@ import { Helmet } from "react-helmet"; import { useTranslation } from "react-i18next"; 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 { getProjectTitle } from "@app/helpers/project"; import { withProjectPermission } from "@app/hoc"; @@ -38,11 +38,11 @@ const Page = withProjectPermission( return (
-
-

- {currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"} Access - Control -

+
+ Users diff --git a/frontend/src/pages/project/AccessControlPage/route-cert-manager.tsx b/frontend/src/pages/project/AccessControlPage/route-cert-manager.tsx index 1bea9fc9e..c74ce4fb1 100644 --- a/frontend/src/pages/project/AccessControlPage/route-cert-manager.tsx +++ b/frontend/src/pages/project/AccessControlPage/route-cert-manager.tsx @@ -1,4 +1,4 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; @@ -14,5 +14,21 @@ export const Route = createFileRoute( "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management" )({ 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 + } + }) + } + ] + }; + } }); diff --git a/frontend/src/pages/project/AccessControlPage/route-kms.tsx b/frontend/src/pages/project/AccessControlPage/route-kms.tsx index 1262dda77..f5f0ab4d9 100644 --- a/frontend/src/pages/project/AccessControlPage/route-kms.tsx +++ b/frontend/src/pages/project/AccessControlPage/route-kms.tsx @@ -1,4 +1,4 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; @@ -14,5 +14,21 @@ export const Route = createFileRoute( "/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/access-management" )({ 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 + } + }) + } + ] + }; + } }); diff --git a/frontend/src/pages/project/AccessControlPage/route-secret-manager.tsx b/frontend/src/pages/project/AccessControlPage/route-secret-manager.tsx index 4b9bb1b9d..318232866 100644 --- a/frontend/src/pages/project/AccessControlPage/route-secret-manager.tsx +++ b/frontend/src/pages/project/AccessControlPage/route-secret-manager.tsx @@ -1,4 +1,4 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; @@ -14,5 +14,21 @@ export const Route = createFileRoute( "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management" )({ 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 + } + }) + } + ] + }; + } }); diff --git a/frontend/src/pages/project/AccessControlPage/route-ssh.tsx b/frontend/src/pages/project/AccessControlPage/route-ssh.tsx index 49e53aa63..fd5966061 100644 --- a/frontend/src/pages/project/AccessControlPage/route-ssh.tsx +++ b/frontend/src/pages/project/AccessControlPage/route-ssh.tsx @@ -1,4 +1,4 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; @@ -14,5 +14,21 @@ export const Route = createFileRoute( "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management" )({ 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 + } + }) + } + ] + }; + } }); diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx index 865a79206..841c8f0d5 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx @@ -4,11 +4,11 @@ import { subject } from "@casl/ability"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 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 { 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 { getProjectTitle } from "@app/helpers/project"; import { usePopUp } from "@app/hooks"; @@ -82,78 +82,37 @@ const Page = () => { } return ( -
-
- -
+
{identityMembershipDetails ? ( <> -
-
-
-

- Project Identity Access -

-
- +
+ + {(isAllowed) => ( + - )} - -
-
-
-
- Name - {identityMembershipDetails && ( -

- {identityMembershipDetails?.identity?.name} -

- )} -
-
-
- Joined on{" "} - {identityMembershipDetails?.createdAt && - format(new Date(identityMembershipDetails?.createdAt || ""), "yyyy-MM-dd")} -
+ Remove Identity + + )} +
-
+ { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Access Control", + link: linkOptions({ + to: "/cert-manager/$projectId/access-management", + params: { + projectId: params.projectId + } + }) + }, + { + label: "Identities" + } + ] + }; + } }); diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/route-kms.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/route-kms.tsx index 77f0e3d44..eedb733b5 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/route-kms.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/route-kms.tsx @@ -1,9 +1,28 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage"; export const Route = createFileRoute( "/_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" + } + ] + }; + } }); diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/route-secret-manager.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/route-secret-manager.tsx index 66e1245e2..46df6c7bf 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/route-secret-manager.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/route-secret-manager.tsx @@ -1,9 +1,28 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage"; export const Route = createFileRoute( "/_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" + } + ] + }; + } }); diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/route-ssh.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/route-ssh.tsx index 8159205c9..e63afa841 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/route-ssh.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/route-ssh.tsx @@ -1,9 +1,28 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage"; export const Route = createFileRoute( "/_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" + } + ] + }; + } }); diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx index c3a714ba9..49b746bc7 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx @@ -1,21 +1,18 @@ import { Helmet } from "react-helmet"; 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 { format } from "date-fns"; +import { formatRelative } from "date-fns"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; 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, useOrganization, useWorkspace } from "@app/context"; -import { getProjectTitle } from "@app/helpers/project"; import { usePopUp } from "@app/hooks"; import { useDeleteUserFromWorkspace, useGetWorkspaceUserDetails } from "@app/hooks/api"; @@ -83,76 +80,36 @@ export const Page = () => { return (
-
- -
{membershipDetails ? ( <> -
-
-
-

Project User Access

-
- - {(isAllowed) => ( - - )} - -
-
-
-
- Name - {membershipDetails && ( -

- {membershipDetails.user.firstName || membershipDetails.user.lastName - ? `${membershipDetails.user.firstName} ${membershipDetails.user.lastName}` - : "-"} -

- )} -
-
- Email - {membershipDetails &&

{membershipDetails?.user?.email}

} -
-
-
- Joined on{" "} - {membershipDetails?.createdAt && - format(new Date(membershipDetails?.createdAt || ""), "yyyy-MM-dd")} -
-
-
+ + + {(isAllowed) => ( + + )} + + { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Access Control", + link: linkOptions({ + to: "/cert-manager/$projectId/access-management", + params: { + projectId: params.projectId + } + }) + }, + { + label: "Users" + } + ] + }; + } }); diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/route-kms.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/route-kms.tsx index 95e7fd025..fd2f240c4 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/route-kms.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/route-kms.tsx @@ -1,9 +1,28 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage"; export const Route = createFileRoute( "/_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" + } + ] + }; + } }); diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/route-secret-manager.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/route-secret-manager.tsx index f5d826e20..7975158b1 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/route-secret-manager.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/route-secret-manager.tsx @@ -1,9 +1,28 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage"; export const Route = createFileRoute( "/_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" + } + ] + }; + } }); diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/route-ssh.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/route-ssh.tsx index 94b597179..bf9788c7a 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/route-ssh.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/route-ssh.tsx @@ -1,9 +1,28 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage"; export const Route = createFileRoute( "/_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" + } + ] + }; + } }); diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx index 522ac4434..cca278230 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx @@ -1,7 +1,5 @@ import { Helmet } from "react-helmet"; 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 { twMerge } from "tailwind-merge"; @@ -14,6 +12,7 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, + PageHeader, Tooltip } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; @@ -83,38 +82,18 @@ const Page = () => { return (
{data && ( -
- -
-

{data.name}

+
+ {isCustomRole && (
- +
- + {
)} -
+
diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/route-cert-manager.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/route-cert-manager.tsx index 7f9ec6791..70492769f 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/route-cert-manager.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/route-cert-manager.tsx @@ -1,9 +1,28 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage"; export const Route = createFileRoute( "/_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" + } + ] + }; + } }); diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/route-kms.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/route-kms.tsx index 1677d9401..2ed018da9 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/route-kms.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/route-kms.tsx @@ -1,9 +1,28 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage"; export const Route = createFileRoute( "/_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" + } + ] + }; + } }); diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/route-secret-manager.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/route-secret-manager.tsx index 4a2f18750..be7d2f7ca 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/route-secret-manager.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/route-secret-manager.tsx @@ -1,9 +1,28 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage"; export const Route = createFileRoute( "/_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" + } + ] + }; + } }); diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/route-ssh.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/route-ssh.tsx index 73b5826d4..f744ab313 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/route-ssh.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/route-ssh.tsx @@ -1,9 +1,28 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage"; export const Route = createFileRoute( "/_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" + } + ] + }; + } }); diff --git a/frontend/src/pages/secret-manager/SettingsPage/SettingsPage.tsx b/frontend/src/pages/secret-manager/SettingsPage/SettingsPage.tsx index c96a37be5..b2c2e406f 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/SettingsPage.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/SettingsPage.tsx @@ -1,7 +1,7 @@ import { Helmet } from "react-helmet"; 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 { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types"; @@ -42,12 +42,9 @@ export const SettingsPage = () => {
{t("common.head-title", { title: t("settings.project.title") })} - -
-
-

{t("settings.project.title")}

-
+
+ {tabs diff --git a/frontend/src/pages/secret-manager/SettingsPage/route.tsx b/frontend/src/pages/secret-manager/SettingsPage/route.tsx index 7996396c8..f826296c5 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/route.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/route.tsx @@ -5,5 +5,15 @@ import { SettingsPage } from "./SettingsPage"; export const Route = createFileRoute( "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/settings" )({ - component: SettingsPage + component: SettingsPage, + beforeLoad: ({ context }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Settings" + } + ] + }; + } }); diff --git a/frontend/src/pages/secret-manager/layout.tsx b/frontend/src/pages/secret-manager/layout.tsx index 824c341b6..0ad0c2954 100644 --- a/frontend/src/pages/secret-manager/layout.tsx +++ b/frontend/src/pages/secret-manager/layout.tsx @@ -1,21 +1,16 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { workspaceKeys } from "@app/hooks/api"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { ProjectLayout } from "@app/layouts/ProjectLayout"; -import { OrganizationLayout } from "@app/layouts/OrganizationLayout"; export const Route = createFileRoute( "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout" )({ - component: () => ( - - - - ), + component: ProjectLayout, beforeLoad: async ({ params, context }) => { - await context.queryClient.ensureQueryData({ + const project = await context.queryClient.ensureQueryData({ queryKey: workspaceKeys.getWorkspaceById(params.projectId), queryFn: () => fetchWorkspaceById(params.projectId) }); @@ -26,5 +21,21 @@ export const Route = createFileRoute( }), 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 } + }) + } + ] + }; } }); diff --git a/frontend/src/pages/ssh/OverviewPage/OverviewPage.tsx b/frontend/src/pages/ssh/OverviewPage/OverviewPage.tsx index 13c3d8ea3..9123bf675 100644 --- a/frontend/src/pages/ssh/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/ssh/OverviewPage/OverviewPage.tsx @@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"; import { motion } from "framer-motion"; 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 { SshCaSection, SshCertificatesSection } from "./components"; @@ -19,12 +19,14 @@ export const OverviewPage = () => { <> {t("common.head-title", { title: "Certificates" })} -
-
-

SSH

+
+ SSH Certificates diff --git a/frontend/src/pages/ssh/SettingsPage/SettingsPage.tsx b/frontend/src/pages/ssh/SettingsPage/SettingsPage.tsx index 674b97009..dce31f01b 100644 --- a/frontend/src/pages/ssh/SettingsPage/SettingsPage.tsx +++ b/frontend/src/pages/ssh/SettingsPage/SettingsPage.tsx @@ -1,7 +1,7 @@ import { Helmet } from "react-helmet"; 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"; @@ -15,10 +15,8 @@ export const SettingsPage = () => { {t("common.head-title", { title: t("settings.project.title") })} -
-
-

{t("settings.project.title")}

-
+
+ {tabs.map((tab) => ( diff --git a/frontend/src/pages/ssh/SettingsPage/route.tsx b/frontend/src/pages/ssh/SettingsPage/route.tsx index 5649fd26c..174064d06 100644 --- a/frontend/src/pages/ssh/SettingsPage/route.tsx +++ b/frontend/src/pages/ssh/SettingsPage/route.tsx @@ -5,5 +5,15 @@ import { SettingsPage } from "./SettingsPage"; export const Route = createFileRoute( "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/settings" )({ - component: SettingsPage + component: SettingsPage, + beforeLoad: ({ context }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Settings" + } + ] + }; + } }); diff --git a/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx b/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx index f8cf9af91..926505230 100644 --- a/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx +++ b/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx @@ -1,6 +1,4 @@ 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 { twMerge } from "tailwind-merge"; @@ -13,6 +11,7 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, + PageHeader, Tooltip } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; @@ -71,34 +70,17 @@ const Page = () => { return (
{data && ( -
- -
-

{data.friendlyName}

+
+
- +
- + {
-
+
diff --git a/frontend/src/pages/ssh/SshCaByIDPage/route.tsx b/frontend/src/pages/ssh/SshCaByIDPage/route.tsx index d8a2a4f9c..3d34bcab0 100644 --- a/frontend/src/pages/ssh/SshCaByIDPage/route.tsx +++ b/frontend/src/pages/ssh/SshCaByIDPage/route.tsx @@ -1,9 +1,25 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { SshCaByIDPage } from "./SshCaByIDPage"; export const Route = createFileRoute( "/_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 + } + }) + } + ] + }; + } }); diff --git a/frontend/src/pages/ssh/layout.tsx b/frontend/src/pages/ssh/layout.tsx index d305bf3d2..d36c263ae 100644 --- a/frontend/src/pages/ssh/layout.tsx +++ b/frontend/src/pages/ssh/layout.tsx @@ -1,21 +1,16 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { workspaceKeys } from "@app/hooks/api"; import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries"; import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries"; import { ProjectLayout } from "@app/layouts/ProjectLayout"; -import { OrganizationLayout } from "@app/layouts/OrganizationLayout"; export const Route = createFileRoute( "/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout" )({ - component: () => ( - - - - ), + component: ProjectLayout, beforeLoad: async ({ params, context }) => { - await context.queryClient.ensureQueryData({ + const project = await context.queryClient.ensureQueryData({ queryKey: workspaceKeys.getWorkspaceById(params.projectId), queryFn: () => fetchWorkspaceById(params.projectId) }); @@ -26,5 +21,21 @@ export const Route = createFileRoute( }), 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 } + }) + } + ] + }; } });