From d82d22a1980b182d8a0b3905a7fd581ec331a876 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 27 Jun 2025 14:02:21 +0530 Subject: [PATCH] feat: seperated layouts for each product line --- frontend/src/layouts/KmsLayout/KmsLayout.tsx | 62 ++++++++ frontend/src/layouts/KmsLayout/index.tsx | 1 + .../PkiManagerLayout/PkiManagerLayout.tsx | 102 +++++++++++++ .../src/layouts/PkiManagerLayout/index.tsx | 1 + .../ProjectGeneralLayout.tsx | 137 ++++++++++++++++++ .../layouts/ProjectGeneralLayout/index.tsx | 1 + .../layouts/ProjectLayout/ProjectLayout.tsx | 135 ++++++++--------- .../SecretManagerLayout.tsx | 11 +- .../SecretScanningLayout.tsx | 62 ++++++++ .../layouts/SecretScanningLayout/index.tsx | 1 + frontend/src/layouts/SshLayout/SshLayout.tsx | 100 +++++++++++++ frontend/src/layouts/SshLayout/index.tsx | 1 + frontend/src/pages/cert-manager/layout.tsx | 49 ++----- frontend/src/pages/kms/layout.tsx | 49 ++----- .../project/SettingsPage/SettingsPage.tsx | 3 + .../src/pages/project/SettingsPage/route.tsx | 24 +++ frontend/src/pages/secret-scanning/layout.tsx | 49 ++----- frontend/src/pages/ssh/layout.tsx | 49 ++----- 18 files changed, 597 insertions(+), 240 deletions(-) create mode 100644 frontend/src/layouts/KmsLayout/KmsLayout.tsx create mode 100644 frontend/src/layouts/KmsLayout/index.tsx create mode 100644 frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx create mode 100644 frontend/src/layouts/PkiManagerLayout/index.tsx create mode 100644 frontend/src/layouts/ProjectGeneralLayout/ProjectGeneralLayout.tsx create mode 100644 frontend/src/layouts/ProjectGeneralLayout/index.tsx create mode 100644 frontend/src/layouts/SecretScanningLayout/SecretScanningLayout.tsx create mode 100644 frontend/src/layouts/SecretScanningLayout/index.tsx create mode 100644 frontend/src/layouts/SshLayout/SshLayout.tsx create mode 100644 frontend/src/layouts/SshLayout/index.tsx create mode 100644 frontend/src/pages/project/SettingsPage/SettingsPage.tsx create mode 100644 frontend/src/pages/project/SettingsPage/route.tsx diff --git a/frontend/src/layouts/KmsLayout/KmsLayout.tsx b/frontend/src/layouts/KmsLayout/KmsLayout.tsx new file mode 100644 index 000000000..fcf6af23e --- /dev/null +++ b/frontend/src/layouts/KmsLayout/KmsLayout.tsx @@ -0,0 +1,62 @@ +import { Link, Outlet } from "@tanstack/react-router"; +import { motion } from "framer-motion"; + +import { Menu, MenuItem } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; + +export const KmsLayout = () => { + const { currentWorkspace } = useWorkspace(); + + return ( +
+
+ + + +
+ +
+
+
+ ); +}; + diff --git a/frontend/src/layouts/KmsLayout/index.tsx b/frontend/src/layouts/KmsLayout/index.tsx new file mode 100644 index 000000000..3846a52e6 --- /dev/null +++ b/frontend/src/layouts/KmsLayout/index.tsx @@ -0,0 +1 @@ +export { KmsLayout } from "./KmsLayout"; \ No newline at end of file diff --git a/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx b/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx new file mode 100644 index 000000000..bc75659dd --- /dev/null +++ b/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx @@ -0,0 +1,102 @@ +import { useTranslation } from "react-i18next"; +import { faMobile } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Link, Outlet } from "@tanstack/react-router"; +import { motion } from "framer-motion"; + +import { Menu, MenuItem } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; + +export const PkiManagerLayout = () => { + const { currentWorkspace } = useWorkspace(); + const { t } = useTranslation(); + + return ( + <> +
+
+ + + +
+ +
+
+
+
+ +

+ {` ${t("common.no-mobile")} `} +

+
+ + ); +}; + diff --git a/frontend/src/layouts/PkiManagerLayout/index.tsx b/frontend/src/layouts/PkiManagerLayout/index.tsx new file mode 100644 index 000000000..caf250f0d --- /dev/null +++ b/frontend/src/layouts/PkiManagerLayout/index.tsx @@ -0,0 +1 @@ +export { PkiManagerLayout } from "./PkiManagerLayout"; \ No newline at end of file diff --git a/frontend/src/layouts/ProjectGeneralLayout/ProjectGeneralLayout.tsx b/frontend/src/layouts/ProjectGeneralLayout/ProjectGeneralLayout.tsx new file mode 100644 index 000000000..c1f8701d1 --- /dev/null +++ b/frontend/src/layouts/ProjectGeneralLayout/ProjectGeneralLayout.tsx @@ -0,0 +1,137 @@ +import { useTranslation } from "react-i18next"; +import { faMobile } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Link, Outlet } from "@tanstack/react-router"; +import { motion } from "framer-motion"; + +import { Badge, Menu, MenuItem } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { + useGetAccessRequestsCount, + useGetSecretApprovalRequestCount, + useGetSecretRotations +} from "@app/hooks/api"; +import { ProjectType } from "@app/hooks/api/workspace/types"; + +// 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 ProjectGeneralLayout = () => { + const { currentWorkspace } = useWorkspace(); + + const { t } = useTranslation(); + const workspaceId = currentWorkspace?.id || ""; + const projectSlug = currentWorkspace?.slug || ""; + + const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ + workspaceId + }); + const { data: accessApprovalRequestCount } = useGetAccessRequestsCount({ + projectSlug + }); + + // we only show the secret rotations v1 tab if they have existing rotations + const { data: secretRotations } = useGetSecretRotations({ + workspaceId, + options: { + refetchOnMount: false + } + }); + + const pendingRequestsCount = + (secretApprovalReqCount?.open || 0) + (accessApprovalRequestCount?.pendingCount || 0); + + return ( + <> +
+
+ + + +
+ +
+
+
+
+ +

+ {` ${t("common.no-mobile")} `} +

+
+ + ); +}; \ No newline at end of file diff --git a/frontend/src/layouts/ProjectGeneralLayout/index.tsx b/frontend/src/layouts/ProjectGeneralLayout/index.tsx new file mode 100644 index 000000000..417259a7d --- /dev/null +++ b/frontend/src/layouts/ProjectGeneralLayout/index.tsx @@ -0,0 +1 @@ +export { ProjectGeneralLayout } from "./ProjectGeneralLayout"; \ No newline at end of file diff --git a/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx b/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx index 8db70ccd9..15b191972 100644 --- a/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx +++ b/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx @@ -1,7 +1,7 @@ import { useTranslation } from "react-i18next"; import { faDotCircle, faHome, faMobile, faWindowMaximize } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Link, Outlet } from "@tanstack/react-router"; +import { Link, Outlet, useLocation } from "@tanstack/react-router"; import { motion } from "framer-motion"; import { @@ -20,6 +20,8 @@ import { useProjectPermission, useWorkspace } from "@app/context"; import { AssumePrivilegeModeBanner } from "./components/AssumePrivilegeModeBanner"; import { useLocalStorageState } from "@app/hooks"; import { ShouldWrap } from "@app/components/utilities/ShouldWrapComponent"; +import { getCurrentProductFromUrl } from "@app/helpers/project"; +import { ProjectType } from "@app/hooks/api/workspace/types"; enum SidebarStyle { Expanded = "expanded", @@ -31,6 +33,7 @@ const MAX_SIDEBAR_SIZE = "220px"; // 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 location = useLocation(); const { currentWorkspace } = useWorkspace(); const [sidebarStyle, setSidebarStyle] = useLocalStorageState( "project-sidebar-style", @@ -45,6 +48,13 @@ export const ProjectLayout = () => { const maxSidebarWidth = sidebarStyle === SidebarStyle.Collapsed ? MIN_SIDEBAR_SIZE : MAX_SIDEBAR_SIZE; + const currentProductType = getCurrentProductFromUrl(location.pathname); + const isSecretManager = currentProductType === ProjectType.SecretManager; + const isPki = currentProductType === ProjectType.CertificateManager; + const isKms = currentProductType === ProjectType.KMS; + const isSsh = currentProductType === ProjectType.SSH; + const isSecretScanning = currentProductType === ProjectType.SecretScanning; + return ( <>
{ to="/projects/$projectId/secret-manager/overview" params={{ projectId: currentWorkspace.id }} > - {({ isActive }) => ( - - } - > - {isActive && ( -
- )} - Secret Manager - - )} + + } + > + {isSecretManager && ( +
+ )} + Secret Manager + { to="/projects/$projectId/cert-manager/subscribers" params={{ projectId: currentWorkspace.id }} > - {({ isActive }) => ( - } - > - {isActive && ( -
- )} - PKI Manager - - )} + } + > + {isPki &&
} + PKI Manager + { to="/projects/$projectId/kms/overview" params={{ projectId: currentWorkspace.id }} > - {({ isActive }) => ( - - } - > - {isActive && ( -
- )} - KMS - - )} + } + > + {isKms &&
} + KMS + { to="/projects/$projectId/ssh/overview" params={{ projectId: currentWorkspace.id }} > - {({ isActive }) => ( - - } - > - {isActive && ( -
- )} - SSH - - )} + + } + > + {isSsh &&
} + SSH + { position="right" > - {({ isActive }) => ( - - } - > - {isActive && ( -
- )} - Secret Scanning - - )} + + } + > + {isSecretScanning && ( +
+ )} + Secret Scanning + diff --git a/frontend/src/layouts/SecretManagerLayout/SecretManagerLayout.tsx b/frontend/src/layouts/SecretManagerLayout/SecretManagerLayout.tsx index cde8f7d52..2a1743339 100644 --- a/frontend/src/layouts/SecretManagerLayout/SecretManagerLayout.tsx +++ b/frontend/src/layouts/SecretManagerLayout/SecretManagerLayout.tsx @@ -11,10 +11,7 @@ import { useGetSecretApprovalRequestCount, useGetSecretRotations } from "@app/hooks/api"; -import { ProjectType } from "@app/hooks/api/workspace/types"; -// 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 SecretManagerLayout = () => { const { currentWorkspace } = useWorkspace(); @@ -59,7 +56,7 @@ export const SecretManagerLayout = () => {
{ )} { {Boolean(secretRotations?.length) && ( { )} { + const { currentWorkspace } = useWorkspace(); + + return ( +
+
+ + + +
+ +
+
+
+ ); +}; + diff --git a/frontend/src/layouts/SecretScanningLayout/index.tsx b/frontend/src/layouts/SecretScanningLayout/index.tsx new file mode 100644 index 000000000..0bf5eaede --- /dev/null +++ b/frontend/src/layouts/SecretScanningLayout/index.tsx @@ -0,0 +1 @@ +export { SecretScanningLayout } from "./SecretScanningLayout"; \ No newline at end of file diff --git a/frontend/src/layouts/SshLayout/SshLayout.tsx b/frontend/src/layouts/SshLayout/SshLayout.tsx new file mode 100644 index 000000000..b7eeed726 --- /dev/null +++ b/frontend/src/layouts/SshLayout/SshLayout.tsx @@ -0,0 +1,100 @@ +import { Link, Outlet } from "@tanstack/react-router"; +import { motion } from "framer-motion"; + +import { Menu, MenuItem } from "@app/components/v2"; +import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionCan } from "@app/components/permissions"; + +export const SshLayout = () => { + const { currentWorkspace } = useWorkspace(); + + return ( +
+
+ + + +
+ +
+
+
+ ); +}; + diff --git a/frontend/src/layouts/SshLayout/index.tsx b/frontend/src/layouts/SshLayout/index.tsx new file mode 100644 index 000000000..de907b09b --- /dev/null +++ b/frontend/src/layouts/SshLayout/index.tsx @@ -0,0 +1 @@ +export { SshLayout } from "./SshLayout"; \ No newline at end of file diff --git a/frontend/src/pages/cert-manager/layout.tsx b/frontend/src/pages/cert-manager/layout.tsx index 9c42439c9..76b1539ff 100644 --- a/frontend/src/pages/cert-manager/layout.tsx +++ b/frontend/src/pages/cert-manager/layout.tsx @@ -1,45 +1,14 @@ -import { createFileRoute, linkOptions } from '@tanstack/react-router' +import { createFileRoute } 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 { PkiManagerLayout } from "@app/layouts/PkiManagerLayout"; export const Route = createFileRoute( - '/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout', + "/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/cert-manager/_cert-manager-layout" )({ - component: ProjectLayout, - beforeLoad: async ({ params, context }) => { - const project = await context.queryClient.ensureQueryData({ - queryKey: workspaceKeys.getWorkspaceById(params.projectId), - queryFn: () => fetchWorkspaceById(params.projectId), - }) - - await context.queryClient.ensureQueryData({ - queryKey: roleQueryKeys.getUserProjectPermissions({ - workspaceId: params.projectId, - }), - queryFn: () => - fetchUserProjectPermissions({ workspaceId: params.projectId }), - }) - + component: PkiManagerLayout, + beforeLoad: async () => { return { - breadcrumbs: [ - { - label: 'Cert Managers', - link: linkOptions({ to: '/organization/cert-manager/overview' }), - }, - { - label: project.name, - link: linkOptions({ - to: '/cert-manager/$projectId/subscribers', - params: { projectId: project.id }, - }), - }, - ], - } - }, -}) + breadcrumbs: [] + }; + } +}); diff --git a/frontend/src/pages/kms/layout.tsx b/frontend/src/pages/kms/layout.tsx index d602bdadc..7a480d0f9 100644 --- a/frontend/src/pages/kms/layout.tsx +++ b/frontend/src/pages/kms/layout.tsx @@ -1,45 +1,14 @@ -import { createFileRoute, linkOptions } from '@tanstack/react-router' +import { createFileRoute } 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 { KmsLayout } from "@app/layouts/KmsLayout"; export const Route = createFileRoute( - '/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/kms/_kms-layout', + "/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/kms/_kms-layout" )({ - component: ProjectLayout, - beforeLoad: async ({ params, context }) => { - const project = await context.queryClient.ensureQueryData({ - queryKey: workspaceKeys.getWorkspaceById(params.projectId), - queryFn: () => fetchWorkspaceById(params.projectId), - }) - - await context.queryClient.ensureQueryData({ - queryKey: roleQueryKeys.getUserProjectPermissions({ - workspaceId: params.projectId, - }), - queryFn: () => - fetchUserProjectPermissions({ workspaceId: params.projectId }), - }) - + component: KmsLayout, + beforeLoad: async () => { return { - breadcrumbs: [ - { - label: 'KMS', - link: linkOptions({ to: '/organization/kms/overview' }), - }, - { - label: project.name, - link: linkOptions({ - to: '/kms/$projectId/overview', - params: { projectId: project.id }, - }), - }, - ], - } - }, -}) + breadcrumbs: [] + }; + } +}); diff --git a/frontend/src/pages/project/SettingsPage/SettingsPage.tsx b/frontend/src/pages/project/SettingsPage/SettingsPage.tsx new file mode 100644 index 000000000..8fb51763a --- /dev/null +++ b/frontend/src/pages/project/SettingsPage/SettingsPage.tsx @@ -0,0 +1,3 @@ +export const SettingsPage = () => { + return
Settings
; +}; diff --git a/frontend/src/pages/project/SettingsPage/route.tsx b/frontend/src/pages/project/SettingsPage/route.tsx new file mode 100644 index 000000000..375de3157 --- /dev/null +++ b/frontend/src/pages/project/SettingsPage/route.tsx @@ -0,0 +1,24 @@ +import { createFileRoute } from "@tanstack/react-router"; +import { SettingsPage } from "./SettingsPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/ssh/_ssh-layout/access-management" +)({ + component: SettingsPage, + 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/secret-scanning/layout.tsx b/frontend/src/pages/secret-scanning/layout.tsx index e7d8f0b1f..65065a469 100644 --- a/frontend/src/pages/secret-scanning/layout.tsx +++ b/frontend/src/pages/secret-scanning/layout.tsx @@ -1,45 +1,14 @@ -import { createFileRoute, linkOptions } from '@tanstack/react-router' +import { createFileRoute } 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 { SecretScanningLayout } from "@app/layouts/SecretScanningLayout"; export const Route = createFileRoute( - '/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-scanning/_secret-scanning-layout', + "/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/secret-scanning/_secret-scanning-layout" )({ - component: ProjectLayout, - beforeLoad: async ({ params, context }) => { - const project = await context.queryClient.ensureQueryData({ - queryKey: workspaceKeys.getWorkspaceById(params.projectId), - queryFn: () => fetchWorkspaceById(params.projectId), - }) - - await context.queryClient.ensureQueryData({ - queryKey: roleQueryKeys.getUserProjectPermissions({ - workspaceId: params.projectId, - }), - queryFn: () => - fetchUserProjectPermissions({ workspaceId: params.projectId }), - }) - + component: SecretScanningLayout, + beforeLoad: async () => { return { - breadcrumbs: [ - { - label: 'Secret Scanning', - link: linkOptions({ to: '/organization/secret-scanning/overview' }), - }, - { - label: project.name, - link: linkOptions({ - to: '/secret-scanning/$projectId/data-sources', - params: { projectId: project.id }, - }), - }, - ], - } - }, -}) + breadcrumbs: [] + }; + } +}); diff --git a/frontend/src/pages/ssh/layout.tsx b/frontend/src/pages/ssh/layout.tsx index 495b618cb..0e05f8b38 100644 --- a/frontend/src/pages/ssh/layout.tsx +++ b/frontend/src/pages/ssh/layout.tsx @@ -1,45 +1,14 @@ -import { createFileRoute, linkOptions } from '@tanstack/react-router' +import { createFileRoute } 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 { SshLayout } from "@app/layouts/SshLayout"; export const Route = createFileRoute( - '/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/ssh/_ssh-layout', + "/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout/ssh/_ssh-layout" )({ - component: ProjectLayout, - beforeLoad: async ({ params, context }) => { - const project = await context.queryClient.ensureQueryData({ - queryKey: workspaceKeys.getWorkspaceById(params.projectId), - queryFn: () => fetchWorkspaceById(params.projectId), - }) - - await context.queryClient.ensureQueryData({ - queryKey: roleQueryKeys.getUserProjectPermissions({ - workspaceId: params.projectId, - }), - queryFn: () => - fetchUserProjectPermissions({ workspaceId: params.projectId }), - }) - + component: SshLayout, + beforeLoad: async () => { return { - breadcrumbs: [ - { - label: 'SSH', - link: linkOptions({ to: '/organization/ssh/overview' }), - }, - { - label: project.name, - link: linkOptions({ - to: '/ssh/$projectId/overview', - params: { projectId: project.id }, - }), - }, - ], - } - }, -}) + breadcrumbs: [] + }; + } +});