diff --git a/frontend/src/components/navigation/SecretDashboardPathBreadcrumb.tsx b/frontend/src/components/navigation/SecretDashboardPathBreadcrumb.tsx new file mode 100644 index 000000000..584a3f6c7 --- /dev/null +++ b/frontend/src/components/navigation/SecretDashboardPathBreadcrumb.tsx @@ -0,0 +1,87 @@ +import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Link } from "@tanstack/react-router"; +import { twMerge } from "tailwind-merge"; + +import { useTimedReset } from "@app/hooks"; +import { ProjectType } from "@app/hooks/api/workspace/types"; + +import { createNotification } from "../notifications"; +import { IconButton, Tooltip } from "../v2"; + +type Props = { + secretPathSegments: string[]; + selectedPathSegmentIndex: number; + environmentSlug: string; + projectId: string; +}; + +export const SecretDashboardPathBreadcrumb = ({ + secretPathSegments, + selectedPathSegmentIndex, + environmentSlug, + projectId +}: Props) => { + const [, isCopying, setIsCopying] = useTimedReset({ + initialState: false + }); + + const newSecretPath = `/${secretPathSegments.slice(0, selectedPathSegmentIndex + 1).join("/")}`; + const isLastItem = secretPathSegments.length === selectedPathSegmentIndex + 1; + const folderName = secretPathSegments.at(selectedPathSegmentIndex); + + return ( +
+ {isLastItem ? ( +
+ + {folderName} + + + { + if (isCopying) return; + setIsCopying(true); + navigator.clipboard.writeText(newSecretPath); + + createNotification({ + text: "Copied secret path to clipboard", + type: "info" + }); + }} + className="opacity-0 transition duration-75 hover:bg-bunker-100/10 group-hover:opacity-100" + > + + + +
+ ) : ( + ({ ...query, secretPath: newSecretPath })} + className={twMerge( + "text-sm font-semibold transition-all hover:text-primary", + isCopying && "text-primary" + )} + > + {folderName} + + )} +
+ ); +}; diff --git a/frontend/src/components/v2/Breadcrumb/Breadcrumb.tsx b/frontend/src/components/v2/Breadcrumb/Breadcrumb.tsx index cd0b1781b..d7b78c67a 100644 --- a/frontend/src/components/v2/Breadcrumb/Breadcrumb.tsx +++ b/frontend/src/components/v2/Breadcrumb/Breadcrumb.tsx @@ -1,8 +1,19 @@ -import * as React from "react"; -import { faChevronRight, faEllipsis } from "@fortawesome/free-solid-svg-icons"; +/* eslint-disable react/prop-types */ +import React from "react"; +import { faCaretDown, faChevronRight, faEllipsis } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Link, ReactNode } from "@tanstack/react-router"; +import { LinkComponentProps } from "node_modules/@tanstack/react-router/dist/esm/link"; import { twMerge } from "tailwind-merge"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger +} from "../Dropdown"; + const Breadcrumb = React.forwardRef< HTMLElement, React.ComponentPropsWithoutRef<"nav"> & { @@ -27,21 +38,25 @@ BreadcrumbList.displayName = "BreadcrumbList"; const BreadcrumbItem = React.forwardRef>( ({ className, ...props }, ref) => ( -
  • +
  • ) ); BreadcrumbItem.displayName = "BreadcrumbItem"; const BreadcrumbLink = React.forwardRef< - HTMLLIElement, - React.ComponentPropsWithoutRef<"li"> & { + HTMLDivElement, + React.ComponentPropsWithoutRef<"div"> & { asChild?: boolean; } >(({ asChild, className, ...props }, ref) => { return ( -
  • ); @@ -87,12 +102,116 @@ const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<"span" ); BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"; +enum BreadcrumbTypes { + Dropdown = "dropdown", + Component = "component" +} + +export type TBreadcrumbFormat = + | { + type: BreadcrumbTypes.Dropdown; + label: string; + dropdownTitle?: string; + links: { label: string; link: LinkComponentProps }[]; + } + | { + type: BreadcrumbTypes.Component; + component: ReactNode; + } + | { + type: undefined; + link?: LinkComponentProps; + label: string; + icon?: ReactNode; + }; + +const BreadcrumbContainer = ({ breadcrumbs }: { breadcrumbs: TBreadcrumbFormat[] }) => ( +
    + + + {(breadcrumbs as TBreadcrumbFormat[]).map((el, index) => { + const isNotLastCrumb = index + 1 !== breadcrumbs.length; + const BreadcrumbSegment = isNotLastCrumb ? BreadcrumbLink : BreadcrumbPage; + + if (el.type === BreadcrumbTypes.Dropdown) { + return ( + + + + + + {el.label} + + + + + {el?.dropdownTitle && {el.dropdownTitle}} + {el.links.map((i, dropIndex) => ( + + {i.label} + + ))} + + + {isNotLastCrumb && } + + ); + } + + if (el.type === BreadcrumbTypes.Component) { + const Component = el.component; + return ( + + + + + + + {isNotLastCrumb && } + + ); + } + + const Icon = el?.icon; + return ( + + {"link" in el && isNotLastCrumb ? ( + + + + {Icon && } + {el.label} + + + + ) : ( + + + {Icon && } + {el.label} + + + )} + {isNotLastCrumb && } + + ); + })} + + +
    +); + export { Breadcrumb, + BreadcrumbContainer, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, - BreadcrumbSeparator + BreadcrumbSeparator, + BreadcrumbTypes }; diff --git a/frontend/src/components/v2/Menu/Menu.tsx b/frontend/src/components/v2/Menu/Menu.tsx index ea9643eb0..6fc2a7d00 100644 --- a/frontend/src/components/v2/Menu/Menu.tsx +++ b/frontend/src/components/v2/Menu/Menu.tsx @@ -1,6 +1,5 @@ import { ComponentPropsWithRef, ElementType, ReactNode, Ref, useRef } from "react"; import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react"; -import { motion } from "framer-motion"; import { twMerge } from "tailwind-merge"; export type MenuProps = { diff --git a/frontend/src/components/v2/PageHeader/PageHeader.tsx b/frontend/src/components/v2/PageHeader/PageHeader.tsx index 3cf550fe9..f07aa2f2e 100644 --- a/frontend/src/components/v2/PageHeader/PageHeader.tsx +++ b/frontend/src/components/v2/PageHeader/PageHeader.tsx @@ -14,8 +14,6 @@ export const PageHeader = ({ title, description, children }: Props) => (
    {children}
    -
    -

    {description}

    -
    +
    {description}
    ); diff --git a/frontend/src/hooks/useToggle.tsx b/frontend/src/hooks/useToggle.tsx index ecc243ade..11e4fc824 100644 --- a/frontend/src/hooks/useToggle.tsx +++ b/frontend/src/hooks/useToggle.tsx @@ -30,8 +30,9 @@ export const useToggle = (initialState = false): UseToggleReturn => { const timedToggle = useCallback((timeout = 2000) => { setValue((prev) => !prev); - setTimeout(() => { + const timeoutRef = setTimeout(() => { setValue(false); + clearTimeout(timeoutRef); }, timeout); }, []); diff --git a/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx b/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx index 4fcf00bc5..974cc3b40 100644 --- a/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx +++ b/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx @@ -1,25 +1,20 @@ -import { ReactNode, useState } from "react"; +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, useRouterState } from "@tanstack/react-router"; -import { AnimatePresence, motion } from "framer-motion"; -import { twMerge } from "tailwind-merge"; +import { motion } from "framer-motion"; import { Mfa } from "@app/components/auth/Mfa"; import { CreateOrgModal } from "@app/components/organization/CreateOrgModal"; import SecurityClient from "@app/components/utilities/SecurityClient"; import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, + BreadcrumbContainer, Menu, MenuGroup, - MenuItem + MenuItem, + TBreadcrumbFormat } from "@app/components/v2"; import { useUser } from "@app/context"; import { usePopUp, useToggle } from "@app/hooks"; @@ -30,15 +25,10 @@ import { ProjectType } from "@app/hooks/api/workspace/types"; import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils"; import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner"; -import { MenuIconButton } from "./components/MenuIconButton"; import { SidebarFooter } from "./components/SidebarFooter"; import { SidebarHeader } from "./components/SidebarHeader"; -type Props = { - children?: ReactNode; -}; - -export const OrganizationLayout = ({ children }: Props) => { +export const OrganizationLayout = () => { const [shouldShowMfa, toggleShowMfa] = useToggle(false); const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL); const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {}); @@ -53,9 +43,6 @@ export const OrganizationLayout = ({ children }: Props) => { const router = useRouter(); const queryClient = useQueryClient(); - const isNestedLayout = Boolean(children); - const [isCollapsed, setIsCollapsed] = useToggle(isNestedLayout); - const { t } = useTranslation(); const handleOrgChange = async (orgId: string) => { queryClient.removeQueries({ queryKey: authKeys.getAuthToken }); @@ -96,168 +83,108 @@ export const OrganizationLayout = ({ children }: Props) => {
    {!window.isSecureContext && }
    -
    diff --git a/frontend/src/layouts/OrganizationLayout/components/SidebarFooter/SidebarFooter.tsx b/frontend/src/layouts/OrganizationLayout/components/SidebarFooter/SidebarFooter.tsx index 5d45850c6..265412cc7 100644 --- a/frontend/src/layouts/OrganizationLayout/components/SidebarFooter/SidebarFooter.tsx +++ b/frontend/src/layouts/OrganizationLayout/components/SidebarFooter/SidebarFooter.tsx @@ -1,13 +1,10 @@ import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons"; import { - faAnglesLeft, - faAnglesRight, faArrowUpRightFromSquare, faBook, faEnvelope, faInfinity, faInfo, - faInfoCircle, faPlus, faQuestion, faUser @@ -20,15 +17,12 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, - DropdownMenuTrigger, - MenuItem + DropdownMenuTrigger } from "@app/components/v2"; import { envConfig } from "@app/config/env"; import { useOrganization, useSubscription, useUser } from "@app/context"; import { useGetOrgTrialUrl, useLogoutUser } from "@app/hooks/api"; -import { MenuIconButton } from "../MenuIconButton"; - export const INFISICAL_SUPPORT_OPTIONS = [ [ , @@ -52,12 +46,7 @@ export const INFISICAL_SUPPORT_OPTIONS = [ ] ]; -type Props = { - isCollapsed?: boolean; - onToggleSidebar: () => void; -}; - -export const SidebarFooter = ({ isCollapsed, onToggleSidebar }: Props) => { +export const SidebarFooter = () => { const { subscription } = useSubscription(); const { currentOrg } = useOrganization(); @@ -82,38 +71,28 @@ export const SidebarFooter = ({ isCollapsed, onToggleSidebar }: Props) => { subscription && subscription.slug === "starter" && !subscription.has_used_trial ? "mb-2" : "mb-4" - } flex w-full cursor-default flex-col items-center ${isCollapsed ? "px-1" : "px-2"} text-sm text-mineshaft-400`} + } flex w-full cursor-default flex-col items-center px-2 text-sm text-mineshaft-400`} > {(window.location.origin.includes("https://app.infisical.com") || - window.location.origin.includes("https://gamma.infisical.com")) && - !isCollapsed && } - {!isCollapsed && ( - -
    - - Invite people -
    - - )} + window.location.origin.includes("https://gamma.infisical.com")) && } + +
    + + Invite people +
    + - {isCollapsed ? ( - - - Support - - ) : ( -
    - - Help & Support -
    - )} +
    + + Help & Support +
    {INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => ( @@ -161,39 +140,19 @@ export const SidebarFooter = ({ isCollapsed, onToggleSidebar }: Props) => { )} - {isCollapsed ? ( - - - - ) : ( - - - Collapse - - )} - - {isCollapsed ? ( + +
    +
    + {user?.firstName?.charAt(0)} +
    +
    + {user?.firstName} {user?.lastName} +
    - -
    - {user?.firstName?.charAt(0)} -
    -
    +
    - ) : ( -
    -
    - {user?.firstName?.charAt(0)} -
    -
    - {user?.firstName} {user?.lastName} -
    -
    - -
    -
    - )} +
    {user?.username}
    diff --git a/frontend/src/layouts/OrganizationLayout/components/SidebarHeader/SidebarHeader.tsx b/frontend/src/layouts/OrganizationLayout/components/SidebarHeader/SidebarHeader.tsx index fe68e8bd4..2412cff60 100644 --- a/frontend/src/layouts/OrganizationLayout/components/SidebarHeader/SidebarHeader.tsx +++ b/frontend/src/layouts/OrganizationLayout/components/SidebarHeader/SidebarHeader.tsx @@ -1,4 +1,4 @@ -import { faCheck, faSort } from "@fortawesome/free-solid-svg-icons"; +import { faCheck, faSignOut, faSort } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; @@ -12,14 +12,12 @@ import { import { useOrganization } from "@app/context"; import { useGetOrganizations, useLogoutUser } from "@app/hooks/api"; import { AuthMethod } from "@app/hooks/api/users/types"; -import { twMerge } from "tailwind-merge"; type Prop = { onChangeOrg: (orgId: string) => void; - isCollapsed?: boolean; }; -export const SidebarHeader = ({ onChangeOrg, isCollapsed }: Prop) => { +export const SidebarHeader = ({ onChangeOrg }: Prop) => { const { currentOrg } = useOrganization(); const navigate = useNavigate(); const { data: orgs } = useGetOrganizations(); @@ -36,38 +34,20 @@ export const SidebarHeader = ({ onChangeOrg, isCollapsed }: Prop) => { }; return ( -
    +
    -
    - {isCollapsed ? ( -
    - {currentOrg?.name.charAt(0)} +
    +
    + {currentOrg?.name.charAt(0)} +
    +
    +
    + {currentOrg?.name}
    - ) : ( - <> -
    - {currentOrg?.name.charAt(0)} -
    -
    -
    - {currentOrg?.name} -
    -
    Free Plan
    -
    - - - )} +
    Free Plan
    +
    +
    @@ -113,139 +93,11 @@ export const SidebarHeader = ({ onChangeOrg, isCollapsed }: Prop) => { ); })}
    - + }> + Log Out +
    ); }; - -// -// -//
    -//
    -// {currentOrg?.name.charAt(0)} -//
    -//
    -// {currentOrg?.name} -//
    -// -//
    -//
    -// -//
    {user?.username}
    -// {orgs?.map((org) => { -// return ( -// -// -// -// ); -// })} -//
    -// -// -// -// -// -//
    -// {user?.firstName?.charAt(0)} -// {user?.lastName && user?.lastName?.charAt(0)} -//
    -//
    -// -//
    {user?.username}
    -// -// Personal Settings -// -// -// -// Documentation -// -// -// -// -// -// Join Slack Community -// -// -// -// {user?.superAdmin && ( -// -// -// Server Admin Console -// -// -// )} -// -// -// Organization Admin Console -// -// -//
    -// -// -// diff --git a/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx b/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx index 62b94bd32..2a1b8bcd3 100644 --- a/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx +++ b/frontend/src/layouts/ProjectLayout/ProjectLayout.tsx @@ -4,21 +4,18 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link, Outlet, useRouterState } from "@tanstack/react-router"; import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, + BreadcrumbContainer, Menu, MenuGroup, - MenuItem + MenuItem, + TBreadcrumbFormat } 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 { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner"; +import { MinimizedOrgSidebar } from "./components/MinimizedOrgSidebar"; import { ProjectSelect } from "./components/ProjectSelect"; // This is a generic layout shared by all types of projects. @@ -54,6 +51,7 @@ export const ProjectLayout = () => {
    {!window.isSecureContext && }
    + -
    - {breadcrumbs && ( -
    - - - {breadcrumbs.map((el, index) => { - const isNotLastCrumb = index + 1 !== breadcrumbs.length; - return ( - <> - {el.link && isNotLastCrumb && !("disabled" in el.link) ? ( - - - {el.label} - - - ) : ( - - {el.label} - - )} - {isNotLastCrumb && } - - ); - })} - - -
    - )} +
    + {breadcrumbs ? ( + + ) : null}
    diff --git a/frontend/src/layouts/OrganizationLayout/components/MenuIconButton/MenuIconButton.tsx b/frontend/src/layouts/ProjectLayout/components/MenuIconButton/MenuIconButton.tsx similarity index 89% rename from frontend/src/layouts/OrganizationLayout/components/MenuIconButton/MenuIconButton.tsx rename to frontend/src/layouts/ProjectLayout/components/MenuIconButton/MenuIconButton.tsx index 0a07a4ef3..a42146bed 100644 --- a/frontend/src/layouts/OrganizationLayout/components/MenuIconButton/MenuIconButton.tsx +++ b/frontend/src/layouts/ProjectLayout/components/MenuIconButton/MenuIconButton.tsx @@ -1,8 +1,8 @@ import { ComponentPropsWithRef, ElementType, useRef } from "react"; import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react"; +import { twMerge } from "tailwind-merge"; import { MenuItemProps } from "@app/components/v2"; -import { twMerge } from "tailwind-merge"; export const MenuIconButton = ({ children, @@ -10,7 +10,7 @@ export const MenuIconButton = ({ className, isDisabled, isSelected, - as: Item = "button", + as: Item = "div", description, // wrapping in forward ref with generic component causes the loss of ts definitions on props inputRef, @@ -35,7 +35,7 @@ export const MenuIconButton = ({
    {icon && (
    @@ -49,7 +49,7 @@ export const MenuIconButton = ({ />
    )} - {children} +
    {children}
    ); }; diff --git a/frontend/src/layouts/OrganizationLayout/components/MenuIconButton/index.tsx b/frontend/src/layouts/ProjectLayout/components/MenuIconButton/index.tsx similarity index 100% rename from frontend/src/layouts/OrganizationLayout/components/MenuIconButton/index.tsx rename to frontend/src/layouts/ProjectLayout/components/MenuIconButton/index.tsx diff --git a/frontend/src/layouts/ProjectLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx b/frontend/src/layouts/ProjectLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx new file mode 100644 index 000000000..04d960e00 --- /dev/null +++ b/frontend/src/layouts/ProjectLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx @@ -0,0 +1,447 @@ +import { useState } from "react"; +import { + faArrowUpRightFromSquare, + faBook, + faCheck, + faCog, + faEllipsis, + faInfinity, + faInfo, + faInfoCircle, + faMoneyBill, + faReply, + faShare, + faSignOut, + faUsers +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useQueryClient } from "@tanstack/react-query"; +import { Link, useNavigate, useRouter } from "@tanstack/react-router"; +import { motion } from "framer-motion"; + +import { Mfa } from "@app/components/auth/Mfa"; +import { CreateOrgModal } from "@app/components/organization/CreateOrgModal"; +import SecurityClient from "@app/components/utilities/SecurityClient"; +import { + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger +} from "@app/components/v2"; +import { envConfig } from "@app/config/env"; +import { useOrganization, useSubscription, useUser } from "@app/context"; +import { usePopUp, useToggle } from "@app/hooks"; +import { + useGetOrganizations, + useGetOrgTrialUrl, + useLogoutUser, + useSelectOrganization, + workspaceKeys +} from "@app/hooks/api"; +import { authKeys } from "@app/hooks/api/auth/queries"; +import { MfaMethod } from "@app/hooks/api/auth/types"; +import { AuthMethod } from "@app/hooks/api/users/types"; +import { ProjectType } from "@app/hooks/api/workspace/types"; +import { INFISICAL_SUPPORT_OPTIONS } from "@app/layouts/OrganizationLayout/components/SidebarFooter/SidebarFooter"; +import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils"; + +import { MenuIconButton } from "../MenuIconButton"; +import { ProjectSwitcher } from "./ProjectSwitcher"; + +export const MinimizedOrgSidebar = () => { + const [shouldShowMfa, toggleShowMfa] = useToggle(false); + const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL); + const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {}); + const { subscription } = useSubscription(); + + const { user } = useUser(); + const { mutateAsync } = useGetOrgTrialUrl(); + + const { currentOrg } = useOrganization(); + const { data: orgs } = useGetOrganizations(); + + const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const); + const { mutateAsync: selectOrganization } = useSelectOrganization(); + const navigate = useNavigate(); + const router = useRouter(); + const queryClient = useQueryClient(); + + 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); + }; + + const logout = useLogoutUser(); + const logOutUser = async () => { + try { + console.log("Logging out..."); + await logout.mutateAsync(); + navigate({ to: "/login" }); + } catch (error) { + console.error(error); + } + }; + + if (shouldShowMfa) { + return ( +
    + toggleShowMfa.off()} + /> +
    + ); + } + + return ( + <> + + handlePopUpToggle("createOrg", false)} + /> + + ); +}; diff --git a/frontend/src/layouts/ProjectLayout/components/MinimizedOrgSidebar/ProjectSwitcher.tsx b/frontend/src/layouts/ProjectLayout/components/MinimizedOrgSidebar/ProjectSwitcher.tsx new file mode 100644 index 000000000..6a69a4834 --- /dev/null +++ b/frontend/src/layouts/ProjectLayout/components/MinimizedOrgSidebar/ProjectSwitcher.tsx @@ -0,0 +1,59 @@ +import { useState } from "react"; +import { faExternalLink, faSearch } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Link } from "@tanstack/react-router"; + +import { DropdownMenuItem, EmptyState, Input } from "@app/components/v2"; +import { getProjectTitle } from "@app/helpers/project"; +import { useGetUserWorkspaces } from "@app/hooks/api"; +import { ProjectType } from "@app/hooks/api/workspace/types"; + +type Props = { + type: ProjectType; +}; + +export const ProjectSwitcher = ({ type }: Props) => { + const { data: workspaces, isPending } = useGetUserWorkspaces({ type }); + const [search, setSearch] = useState(""); + + const filteredWorkspaces = workspaces?.filter((el) => + el.name.toLowerCase().includes(search.toLowerCase()) + ); + + return ( + <> + +
    + {getProjectTitle(type)} projects + +
    + +
    + } + value={search} + onChange={(evt) => setSearch(evt.target.value)} + size="xs" + placeholder="Search by name" + className="" + /> +
    +
    + {filteredWorkspaces?.map((el) => ( + + + {el.name} + + + ))} + {!isPending && !filteredWorkspaces?.length && ( + + )} +
    + + ); +}; diff --git a/frontend/src/layouts/ProjectLayout/components/MinimizedOrgSidebar/index.tsx b/frontend/src/layouts/ProjectLayout/components/MinimizedOrgSidebar/index.tsx new file mode 100644 index 000000000..f8df49034 --- /dev/null +++ b/frontend/src/layouts/ProjectLayout/components/MinimizedOrgSidebar/index.tsx @@ -0,0 +1 @@ +export { MinimizedOrgSidebar } from "./MinimizedOrgSidebar"; diff --git a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx index ba3bc311a..75d0ca139 100644 --- a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx +++ b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.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"; @@ -91,7 +89,7 @@ const Page = () => {
    - + { const { t } = useTranslation(); diff --git a/frontend/src/pages/organization/AccessManagementPage/route.tsx b/frontend/src/pages/organization/AccessManagementPage/route.tsx index 7dffb1495..7caa58de4 100644 --- a/frontend/src/pages/organization/AccessManagementPage/route.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/route.tsx @@ -1,3 +1,5 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; @@ -23,7 +25,8 @@ export const Route = createFileRoute( context: () => ({ breadcrumbs: [ { - label: "home", + label: "Home", + icon: () => , link: linkOptions({ to: "/" }) }, { diff --git a/frontend/src/pages/organization/AdminPage/route.tsx b/frontend/src/pages/organization/AdminPage/route.tsx index b195c778e..0ab2a530f 100644 --- a/frontend/src/pages/organization/AdminPage/route.tsx +++ b/frontend/src/pages/organization/AdminPage/route.tsx @@ -1,3 +1,5 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { AdminPage } from "./AdminPage"; @@ -9,7 +11,8 @@ export const Route = createFileRoute( context: () => ({ breadcrumbs: [ { - label: "home", + label: "Home", + icon: () => , link: linkOptions({ to: "/organization/secret-manager/overview" }) }, { diff --git a/frontend/src/pages/organization/AuditLogsPage/AuditLogsPage.tsx b/frontend/src/pages/organization/AuditLogsPage/AuditLogsPage.tsx index 688a60457..737a09b66 100644 --- a/frontend/src/pages/organization/AuditLogsPage/AuditLogsPage.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/AuditLogsPage.tsx @@ -1,8 +1,9 @@ import { Helmet } from "react-helmet"; -import { LogsSection } from "./components"; import { PageHeader } from "@app/components/v2"; +import { LogsSection } from "./components"; + export const AuditLogsPage = () => { return (
    diff --git a/frontend/src/pages/organization/AuditLogsPage/route.tsx b/frontend/src/pages/organization/AuditLogsPage/route.tsx index 0437b1271..b9f96b0ec 100644 --- a/frontend/src/pages/organization/AuditLogsPage/route.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/route.tsx @@ -1,3 +1,5 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { AuditLogsPage } from "./AuditLogsPage"; @@ -9,7 +11,8 @@ export const Route = createFileRoute( context: () => ({ breadcrumbs: [ { - label: "home", + label: "Home", + icon: () => , link: linkOptions({ to: "/organization/secret-manager/overview" }) }, { diff --git a/frontend/src/pages/organization/CertManagerOverviewPage/route.tsx b/frontend/src/pages/organization/CertManagerOverviewPage/route.tsx index 1a1c17fc9..21dc45fbc 100644 --- a/frontend/src/pages/organization/CertManagerOverviewPage/route.tsx +++ b/frontend/src/pages/organization/CertManagerOverviewPage/route.tsx @@ -1,3 +1,5 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { CertManagerOverviewPage } from "./CertManagerOverviewPage"; @@ -9,8 +11,8 @@ export const Route = createFileRoute( context: () => ({ breadcrumbs: [ { - label: "products", - link: linkOptions({ disabled: true, to: "/" }) + label: "Products", + icon: () => }, { label: "Cert Management", diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/route.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/route.tsx index f5c43cb77..c977b3768 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/route.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/route.tsx @@ -1,3 +1,5 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage"; @@ -9,7 +11,8 @@ export const Route = createFileRoute( context: () => ({ breadcrumbs: [ { - label: "home", + label: "Home", + icon: () => , link: linkOptions({ to: "/organization/secret-manager/overview" }) }, { diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/route.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/route.tsx index 58a50bd16..d2f4c40a0 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/route.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/route.tsx @@ -1,3 +1,5 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage"; @@ -9,7 +11,8 @@ export const Route = createFileRoute( context: () => ({ breadcrumbs: [ { - label: "home", + label: "Home", + icon: () => , link: linkOptions({ to: "/organization/secret-manager/overview" }) }, { diff --git a/frontend/src/pages/organization/KmsOverviewPage/route.tsx b/frontend/src/pages/organization/KmsOverviewPage/route.tsx index 42ab611bd..bfb7c9cd1 100644 --- a/frontend/src/pages/organization/KmsOverviewPage/route.tsx +++ b/frontend/src/pages/organization/KmsOverviewPage/route.tsx @@ -1,3 +1,5 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { KmsOverviewPage } from "./KmsOverviewPage"; @@ -9,8 +11,8 @@ export const Route = createFileRoute( context: () => ({ breadcrumbs: [ { - label: "products", - link: linkOptions({ disabled: true, to: "/" }) + label: "Products", + icon: () => }, { label: "KMS", diff --git a/frontend/src/pages/organization/RoleByIDPage/route.tsx b/frontend/src/pages/organization/RoleByIDPage/route.tsx index 250478f19..fe41a9b7d 100644 --- a/frontend/src/pages/organization/RoleByIDPage/route.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/route.tsx @@ -1,3 +1,5 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { RoleByIDPage } from "./RoleByIDPage"; @@ -9,7 +11,8 @@ export const Route = createFileRoute( context: () => ({ breadcrumbs: [ { - label: "home", + label: "Home", + icon: () => , link: linkOptions({ to: "/organization/secret-manager/overview" }) }, { diff --git a/frontend/src/pages/organization/SecretManagerOverviewPage/SecretManagerOverviewPage.tsx b/frontend/src/pages/organization/SecretManagerOverviewPage/SecretManagerOverviewPage.tsx index dc89362b5..7d3f7009a 100644 --- a/frontend/src/pages/organization/SecretManagerOverviewPage/SecretManagerOverviewPage.tsx +++ b/frontend/src/pages/organization/SecretManagerOverviewPage/SecretManagerOverviewPage.tsx @@ -366,7 +366,7 @@ export const ProductOverviewPage = ({ type }: Props) => { } return ( -
    +
    {t("common.head-title", { title: t("settings.members.title") })} diff --git a/frontend/src/pages/organization/SecretManagerOverviewPage/route.tsx b/frontend/src/pages/organization/SecretManagerOverviewPage/route.tsx index 10bf601e1..4d02c1795 100644 --- a/frontend/src/pages/organization/SecretManagerOverviewPage/route.tsx +++ b/frontend/src/pages/organization/SecretManagerOverviewPage/route.tsx @@ -1,4 +1,6 @@ -import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { createFileRoute } from "@tanstack/react-router"; import { SecretManagerOverviewPage } from "./SecretManagerOverviewPage"; @@ -9,12 +11,11 @@ export const Route = createFileRoute( context: () => ({ breadcrumbs: [ { - label: "products", - link: linkOptions({ disabled: true, to: "/" }) + label: "Products", + icon: () => }, { - label: "Secret Management", - link: linkOptions({ to: "/organization/secret-manager/overview" }) + label: "Secret Management" } ] }) diff --git a/frontend/src/pages/organization/SecretScanningPage/route.tsx b/frontend/src/pages/organization/SecretScanningPage/route.tsx index 27dbece84..c25805439 100644 --- a/frontend/src/pages/organization/SecretScanningPage/route.tsx +++ b/frontend/src/pages/organization/SecretScanningPage/route.tsx @@ -1,3 +1,5 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; @@ -25,11 +27,12 @@ export const Route = createFileRoute( context: () => ({ breadcrumbs: [ { - label: "home", + label: "Home", + icon: () => , link: linkOptions({ to: "/" }) }, { - label: "secret scanning" + label: "Secret Scanning" } ] }) diff --git a/frontend/src/pages/organization/SecretSharingPage/SecretSharingPage.tsx b/frontend/src/pages/organization/SecretSharingPage/SecretSharingPage.tsx index 8270d9cdc..2eee282db 100644 --- a/frontend/src/pages/organization/SecretSharingPage/SecretSharingPage.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/SecretSharingPage.tsx @@ -3,9 +3,10 @@ import { useTranslation } from "react-i18next"; import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { ShareSecretSection } from "./components"; import { PageHeader } from "@app/components/v2"; +import { ShareSecretSection } from "./components"; + export const SecretSharingPage = () => { const { t } = useTranslation(); diff --git a/frontend/src/pages/organization/SecretSharingPage/route.tsx b/frontend/src/pages/organization/SecretSharingPage/route.tsx index 1389edd40..26f1126ac 100644 --- a/frontend/src/pages/organization/SecretSharingPage/route.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/route.tsx @@ -1,3 +1,5 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { SecretSharingPage } from "./SecretSharingPage"; @@ -9,7 +11,8 @@ export const Route = createFileRoute( context: () => ({ breadcrumbs: [ { - label: "home", + label: "Home", + icon: () => , link: linkOptions({ to: "/" }) }, { diff --git a/frontend/src/pages/organization/SettingsPage/SettingsPage.tsx b/frontend/src/pages/organization/SettingsPage/SettingsPage.tsx index 454c7c100..263672313 100644 --- a/frontend/src/pages/organization/SettingsPage/SettingsPage.tsx +++ b/frontend/src/pages/organization/SettingsPage/SettingsPage.tsx @@ -1,9 +1,10 @@ import { Helmet } from "react-helmet"; import { useTranslation } from "react-i18next"; -import { OrgTabGroup } from "./components"; import { PageHeader } from "@app/components/v2"; +import { OrgTabGroup } from "./components"; + export const SettingsPage = () => { const { t } = useTranslation(); diff --git a/frontend/src/pages/organization/SettingsPage/route.tsx b/frontend/src/pages/organization/SettingsPage/route.tsx index 40ac5b53f..161cede84 100644 --- a/frontend/src/pages/organization/SettingsPage/route.tsx +++ b/frontend/src/pages/organization/SettingsPage/route.tsx @@ -1,3 +1,5 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; @@ -19,7 +21,8 @@ export const Route = createFileRoute( context: () => ({ breadcrumbs: [ { - label: "home", + label: "Home", + icon: () => , link: linkOptions({ to: "/" }) }, { diff --git a/frontend/src/pages/organization/SshOverviewPage/route.tsx b/frontend/src/pages/organization/SshOverviewPage/route.tsx index e23f9bab3..fd1441a21 100644 --- a/frontend/src/pages/organization/SshOverviewPage/route.tsx +++ b/frontend/src/pages/organization/SshOverviewPage/route.tsx @@ -1,3 +1,5 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { SshOverviewPage } from "./SshOverviewPage"; @@ -9,8 +11,8 @@ export const Route = createFileRoute( context: () => ({ breadcrumbs: [ { - label: "products", - link: linkOptions({ disabled: true, to: "/" }) + label: "Products", + icon: () => }, { label: "SSH", diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/route.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/route.tsx index 4806c9124..f2760c357 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/route.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/route.tsx @@ -1,3 +1,5 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { UserDetailsByIDPage } from "./UserDetailsByIDPage"; @@ -9,7 +11,8 @@ export const Route = createFileRoute( context: () => ({ breadcrumbs: [ { - label: "home", + label: "Home", + icon: () => , link: linkOptions({ to: "/organization/secret-manager/overview" }) }, { diff --git a/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx b/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx index d95ccddf0..219de8305 100644 --- a/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx +++ b/frontend/src/pages/project/AccessControlPage/AccessControlPage.tsx @@ -4,7 +4,6 @@ import { useNavigate, useSearch } from "@tanstack/react-router"; 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"; import { ProjectType } from "@app/hooks/api/workspace/types"; import { ProjectAccessControlTabs } from "@app/types/project"; diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx index 841c8f0d5..80ae5dd2e 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx @@ -1,16 +1,13 @@ import { Helmet } from "react-helmet"; import { useTranslation } from "react-i18next"; 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, formatRelative } from "date-fns"; +import { formatRelative } from "date-fns"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; 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"; import { useDeleteIdentityFromWorkspace, diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx index 49b746bc7..790214141 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx @@ -79,7 +79,7 @@ export const Page = () => { } return ( -
    +
    {membershipDetails ? ( <> { ); return ( -
    +
    {view === IntegrationView.List ? ( )}
    -
    +
    {onViewActiveIntegrations && (
    -
    +
    {isLoading && Array.from({ length: 12 }).map((_, index) => ( diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/FrameworkIntegrationSection/FrameworkIntegrationSection.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/FrameworkIntegrationSection/FrameworkIntegrationSection.tsx index 282b30311..211759dcc 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/FrameworkIntegrationSection/FrameworkIntegrationSection.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/FrameworkIntegrationSection/FrameworkIntegrationSection.tsx @@ -12,11 +12,11 @@ export const FrameworkIntegrationSection = () => { return ( <> -
    +

    {t("integrations.framework-integrations")}

    {t("integrations.click-to-setup")}

    -
    +
    {sortedFrameworks.map((framework) => ( { return ( <> -
    +

    Infrastructure Integrations

    Click on of the integration to read the documentation.

    -
    +
    {sortedIntegrations.map((integration) => ( { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Integrations" + } + ] + }; + } }); diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index 308f5f1a6..6f9d4c9b2 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -19,7 +19,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link, useNavigate, useRouter, useSearch } from "@tanstack/react-router"; import { twMerge } from "tailwind-merge"; -import NavHeader from "@app/components/navigation/NavHeader"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { @@ -34,6 +33,7 @@ import { IconButton, Modal, ModalContent, + PageHeader, Pagination, Table, TableContainer, @@ -661,22 +661,17 @@ export const OverviewPage = () => { ); return ( -
    +
    {t("common.head-title", { title: t("dashboard.title") })} - - -
    -
    - -
    -
    -
    -
    -

    Secrets Overview

    +
    -
    - -
    - {userAvailableEnvs.length > 0 && ( - - - - - - - - - - {/* + } + /> +
    +
    + +
    + {userAvailableEnvs.length > 0 && ( + + + + + + + + + + {/* */} - Filter project resources - { - e.preventDefault(); - handleToggleRowType(RowType.Folder); - }} - icon={filter[RowType.Folder] && } - iconPos="right" - > -
    - - Folders -
    -
    - { - e.preventDefault(); - handleToggleRowType(RowType.DynamicSecret); - }} - icon={ - filter[RowType.DynamicSecret] && - } - iconPos="right" - > -
    - - Dynamic Secrets -
    -
    - { - e.preventDefault(); - handleToggleRowType(RowType.Secret); - }} - icon={filter[RowType.Secret] && } - iconPos="right" - > -
    - - Secrets -
    -
    - Choose visible environments - {userAvailableEnvs.map((availableEnv) => { - const { id: envId, name } = availableEnv; + Filter project resources + { + e.preventDefault(); + handleToggleRowType(RowType.Folder); + }} + icon={filter[RowType.Folder] && } + iconPos="right" + > +
    + + Folders +
    +
    + { + e.preventDefault(); + handleToggleRowType(RowType.DynamicSecret); + }} + icon={filter[RowType.DynamicSecret] && } + iconPos="right" + > +
    + + Dynamic Secrets +
    +
    + { + e.preventDefault(); + handleToggleRowType(RowType.Secret); + }} + icon={filter[RowType.Secret] && } + iconPos="right" + > +
    + + Secrets +
    +
    + Choose visible environments + {userAvailableEnvs.map((availableEnv) => { + const { id: envId, name } = availableEnv; - const isEnvSelected = visibleEnvs.map((env) => env.id).includes(envId); - return ( - { - e.preventDefault(); - handleEnvSelect(envId); - }} - key={envId} - disabled={visibleEnvs?.length === 1} - icon={isEnvSelected && } - iconPos="right" - > -
    {name}
    -
    - ); - })} + const isEnvSelected = visibleEnvs.map((env) => env.id).includes(envId); + return ( + { + e.preventDefault(); + handleEnvSelect(envId); + }} + key={envId} + disabled={visibleEnvs?.length === 1} + icon={isEnvSelected && } + iconPos="right" + > +
    {name}
    +
    + ); + })} +
    +
    + )} + + {userAvailableEnvs.length > 0 && ( +
    + + handlePopUpToggle("misc", isOpen)} + > + + + + + + +
    + + {(isAllowed) => ( + + )} + +
    - )} - - {userAvailableEnvs.length > 0 && ( -
    - - handlePopUpToggle("misc", isOpen)} - > - - - - - - -
    - - {(isAllowed) => ( - - )} - -
    -
    -
    -
    - )} -
    +
    + )}
    { : TabSection.SecretApprovalRequests; return ( -
    +
    {t("common.head-title", { title: t("approval.title") })} - - -
    - +
    + + + + Documentation + + + + diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/route.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/route.tsx index 6d26a3a2f..94d8f9035 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/route.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/route.tsx @@ -12,5 +12,15 @@ export const Route = createFileRoute( "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/approval" )({ component: SecretApprovalsPage, - validateSearch: zodValidator(SecretApprovalPageQueryParams) + validateSearch: zodValidator(SecretApprovalPageQueryParams), + beforeLoad: ({ context }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Approvals" + } + ] + }; + } }); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx index 48708d67d..daf875a78 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx @@ -7,7 +7,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; import { twMerge } from "tailwind-merge"; -import NavHeader from "@app/components/navigation/NavHeader"; import { createNotification } from "@app/components/notifications"; import { PermissionDeniedBanner } from "@app/components/permissions"; import { @@ -67,7 +66,6 @@ const LOADER_TEXT = [ ]; const Page = () => { - const { t } = useTranslation(); const { currentWorkspace } = useWorkspace(); const navigate = useNavigate({ from: ROUTE_PATHS.SecretManager.SecretDashboardPage.path @@ -264,18 +262,6 @@ const Page = () => { state === OrderByDirection.ASC ? OrderByDirection.DESC : OrderByDirection.ASC ); - const handleEnvChange = (slug: string) => { - navigate({ - params: { - envSlug: slug - }, - search: (state) => { - const newState = { ...state, secretPath: undefined }; - return newState; - } - }); - }; - const handleTagToggle = useCallback( (tagSlug: string) => setFilter((state) => { @@ -396,21 +382,8 @@ const Page = () => { setDebouncedSearchFilter(""); }; return ( -
    +
    -
    - -
    {!isRollbackMode ? ( <> { isSnapshotCountLoading={isSnapshotCountLoading} onToggleRowType={handleToggleRowType} onClickRollbackMode={() => handlePopUpToggle("snapshots", true)} + protectedBranchPolicyName={boardPolicy?.name} />
    diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx index 81c936bc7..e4de76198 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx @@ -15,6 +15,7 @@ import { faFolder, faFolderPlus, faKey, + faLock, faMinusSquare, faPlus, faTrash @@ -80,6 +81,7 @@ type Props = { isVisible?: boolean; snapshotCount: number; isSnapshotCountLoading?: boolean; + protectedBranchPolicyName?: string; onSearchChange: (term: string) => void; onToggleTagFilter: (tagId: string) => void; onVisibilityToggle: () => void; @@ -101,7 +103,8 @@ export const ActionBar = ({ onToggleTagFilter, onVisibilityToggle, onClickRollbackMode, - onToggleRowType + onToggleRowType, + protectedBranchPolicyName }: Props) => { const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([ "addFolder", @@ -112,9 +115,9 @@ export const ActionBar = ({ "misc", "upgradePlan" ] as const); + const isProtectedBranch = Boolean(protectedBranchPolicyName); const { subscription } = useSubscription(); const { openPopUp } = usePopUpAction(); - const { mutateAsync: createFolder } = useCreateFolder(); const { mutateAsync: deleteBatchSecretV3 } = useDeleteSecretBatch(); const { mutateAsync: moveSecrets } = useMoveSecrets(); @@ -387,6 +390,15 @@ export const ActionBar = ({
    +
    + {isProtectedBranch && ( + + + + + + )} +
    diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/route.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/route.tsx index 9a5821124..ed6ffadb3 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/route.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/route.tsx @@ -1,7 +1,10 @@ -import { createFileRoute, stripSearchParams } from "@tanstack/react-router"; +import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { SecretDashboardPathBreadcrumb } from "@app/components/navigation/SecretDashboardPathBreadcrumb"; +import { BreadcrumbTypes } from "@app/components/v2"; + import { SecretDashboardPage } from "./SecretDashboardPage"; const SecretDashboardPageQueryParamsSchema = z.object({ @@ -9,7 +12,6 @@ const SecretDashboardPageQueryParamsSchema = z.object({ search: z.string().catch(""), tags: z.string().catch("") }); - export const Route = createFileRoute( "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug" )({ @@ -17,5 +19,39 @@ export const Route = createFileRoute( validateSearch: zodValidator(SecretDashboardPageQueryParamsSchema), search: { middlewares: [stripSearchParams({ secretPath: "/", search: "", tags: "" })] + }, + beforeLoad: ({ context, params, search }) => { + const secretPathSegments = search.secretPath.split("/").filter(Boolean); + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + type: BreadcrumbTypes.Dropdown, + label: context.project.environments.find((el) => el.slug === params.envSlug)?.name || "", + dropdownTitle: "Environments", + links: context.project.environments.map((el) => ({ + label: el.name, + link: linkOptions({ + to: "/secret-manager/$projectId/secrets/$envSlug", + params: { + projectId: params.projectId, + envSlug: el.slug + } + }) + })) + }, + ...secretPathSegments.map((_, index) => ({ + type: BreadcrumbTypes.Component, + component: () => ( + + ) + })) + ] + }; } }); diff --git a/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx b/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx index ef66cd069..c27d34cc4 100644 --- a/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx +++ b/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx @@ -20,6 +20,7 @@ import { DeleteActionModal, EmptyState, IconButton, + PageHeader, Skeleton, Spinner, Table, @@ -137,30 +138,25 @@ const Page = () => { }; return ( -
    -
    -
    -

    Secret Rotation

    -

    - Stop manually rotating secrets and automate credential rotation. -

    -
    - -
    +
    + + + + Documentation + + + +
    Rotated Secrets
    @@ -374,7 +370,7 @@ export const SecretRotationPage = () => { const { t } = useTranslation(); return ( -
    +
    {t("common.head-title", { title: t("settings.project.title") })} diff --git a/frontend/src/pages/secret-manager/SecretRotationPage/route.tsx b/frontend/src/pages/secret-manager/SecretRotationPage/route.tsx index ad0c79c79..9cf72c6a8 100644 --- a/frontend/src/pages/secret-manager/SecretRotationPage/route.tsx +++ b/frontend/src/pages/secret-manager/SecretRotationPage/route.tsx @@ -5,5 +5,15 @@ import { SecretRotationPage } from "./SecretRotationPage"; export const Route = createFileRoute( "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secret-rotation" )({ - component: SecretRotationPage + component: SecretRotationPage, + beforeLoad: ({ context }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Secret Rotation" + } + ] + }; + } }); diff --git a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultAuthorizePage/AzureKeyVaultAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultAuthorizePage/AzureKeyVaultAuthorizePage.tsx index 419aa30bf..cbaa54eda 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultAuthorizePage/AzureKeyVaultAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultAuthorizePage/AzureKeyVaultAuthorizePage.tsx @@ -1,13 +1,12 @@ +import { Helmet } from "react-helmet"; import { Controller, useForm } from "react-hook-form"; - import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; +import { useSearch } from "@tanstack/react-router"; import z from "zod"; import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; -import { Helmet } from "react-helmet"; -import { useSearch } from "@tanstack/react-router"; import { ROUTE_PATHS } from "@app/const/routes"; const schema = z.object({ @@ -18,7 +17,7 @@ type FormData = z.infer; export function AzureKeyVaultAuthorizePage() { const { state, clientId } = useSearch({ - from: ROUTE_PATHS.SecretManager.Integratons.AzureKeyVaultAuthorizePage.id, + from: ROUTE_PATHS.SecretManager.Integratons.AzureKeyVaultAuthorizePage.id }); const { control, handleSubmit } = useForm({ @@ -38,7 +37,7 @@ export function AzureKeyVaultAuthorizePage() { return (
    - Authorize Azure Key Vault Integration + Authorize Azure Key Vault Integration @@ -56,12 +55,12 @@ export function AzureKeyVaultAuthorizePage() { target="_blank" rel="noopener noreferrer" > -
    +
    Docs
    diff --git a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultAuthorizePage/route.tsx index cb8c47764..a06dbd091 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultAuthorizePage/route.tsx @@ -1,12 +1,11 @@ import { createFileRoute } from "@tanstack/react-router"; -import z from 'zod' +import z from "zod"; import { AzureKeyVaultAuthorizePage } from "./AzureKeyVaultAuthorizePage"; - const PageQueryParamsSchema = z.object({ state: z.string(), - clientId: z.string().optional(), + clientId: z.string().optional() }); export const Route = createFileRoute( diff --git a/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx index 36a3b9964..e91a3ca89 100644 --- a/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx @@ -187,7 +187,7 @@ export const CircleCIConfigurePage = () => { name="secretPath" render={({ field, fieldState: { error } }) => ( - + )} /> diff --git a/frontend/src/pages/secret-manager/layout.tsx b/frontend/src/pages/secret-manager/layout.tsx index 0ad0c2954..e513d2102 100644 --- a/frontend/src/pages/secret-manager/layout.tsx +++ b/frontend/src/pages/secret-manager/layout.tsx @@ -1,3 +1,5 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { workspaceKeys } from "@app/hooks/api"; @@ -23,9 +25,11 @@ export const Route = createFileRoute( }); return { + project, breadcrumbs: [ { label: "Secret Managers", + icon: () => , link: linkOptions({ to: "/organization/secret-manager/overview" }) }, {