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 = () => {