mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 22:27:22 +00:00
feat: completed sidebar changes
This commit is contained in:
@@ -38,46 +38,46 @@ export const MenuItem = <T extends ElementType = "button">({
|
|||||||
}: MenuItemProps<T> & ComponentPropsWithRef<T>): JSX.Element => {
|
}: MenuItemProps<T> & ComponentPropsWithRef<T>): JSX.Element => {
|
||||||
const iconRef = useRef<DotLottie | null>(null);
|
const iconRef = useRef<DotLottie | null>(null);
|
||||||
return (
|
return (
|
||||||
<div onMouseEnter={() => iconRef.current?.play()} onMouseLeave={() => iconRef.current?.stop()}>
|
<li
|
||||||
<li
|
onMouseEnter={() => iconRef.current?.play()}
|
||||||
className={twMerge(
|
onMouseLeave={() => iconRef.current?.stop()}
|
||||||
"duration-50 group mt-0.5 flex cursor-pointer flex-col rounded px-1 py-2 font-inter text-sm text-bunker-100 transition-all hover:bg-mineshaft-700",
|
className={twMerge(
|
||||||
isSelected && "bg-mineshaft-600 hover:bg-mineshaft-600",
|
"duration-50 group mt-0.5 flex cursor-pointer flex-col rounded px-1 py-2 font-inter text-sm text-bunker-100 transition-all hover:bg-mineshaft-700",
|
||||||
isDisabled && "cursor-not-allowed hover:bg-transparent",
|
isSelected && "bg-mineshaft-600 hover:bg-mineshaft-600",
|
||||||
className
|
isDisabled && "cursor-not-allowed hover:bg-transparent",
|
||||||
)}
|
className
|
||||||
>
|
)}
|
||||||
<motion.span className="flex w-full flex-row items-center justify-start rounded-sm">
|
>
|
||||||
<Item
|
<motion.span className="flex w-full flex-row items-center justify-start rounded-sm">
|
||||||
type="button"
|
<Item
|
||||||
role="menuitem"
|
type="button"
|
||||||
className="relative flex items-center"
|
role="menuitem"
|
||||||
ref={inputRef}
|
className="relative flex items-center"
|
||||||
{...props}
|
ref={inputRef}
|
||||||
>
|
{...props}
|
||||||
<div
|
>
|
||||||
className={`${
|
<div
|
||||||
isSelected ? "visisble" : "invisible"
|
className={`${
|
||||||
} absolute -left-[0.28rem] h-5 w-[0.07rem] rounded-md bg-primary`}
|
isSelected ? "visisble" : "invisible"
|
||||||
/>
|
} absolute -left-[0.28rem] h-5 w-[0.07rem] rounded-md bg-primary`}
|
||||||
{icon && (
|
/>
|
||||||
<div style={{ width: "22px", height: "22px" }} className="my-auto ml-1 mr-3">
|
{icon && (
|
||||||
<DotLottieReact
|
<div style={{ width: "22px", height: "22px" }} className="my-auto ml-1 mr-3">
|
||||||
dotLottieRefCallback={(el) => {
|
<DotLottieReact
|
||||||
iconRef.current = el;
|
dotLottieRefCallback={(el) => {
|
||||||
}}
|
iconRef.current = el;
|
||||||
src={`/lotties/${icon}.json`}
|
}}
|
||||||
loop
|
src={`/lotties/${icon}.json`}
|
||||||
className="h-full w-full"
|
loop
|
||||||
/>
|
className="h-full w-full"
|
||||||
</div>
|
/>
|
||||||
)}
|
</div>
|
||||||
<span className="flex-grow text-left">{children}</span>
|
)}
|
||||||
</Item>
|
<span className="flex-grow text-left">{children}</span>
|
||||||
{description && <span className="mt-2 text-xs">{description}</span>}
|
</Item>
|
||||||
</motion.span>
|
{description && <span className="mt-2 text-xs">{description}</span>}
|
||||||
</li>
|
</motion.span>
|
||||||
</div>
|
</li>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ export type MenuGroupProps = {
|
|||||||
|
|
||||||
export const MenuGroup = ({ children, title }: MenuGroupProps): JSX.Element => (
|
export const MenuGroup = ({ children, title }: MenuGroupProps): JSX.Element => (
|
||||||
<>
|
<>
|
||||||
<li className="p-2 text-xs text-gray-400">{title}</li>
|
<li className="px-2 pt-3 text-xs uppercase text-gray-400">{title}</li>
|
||||||
{children}
|
{children}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { useState } from "react";
|
import { ReactNode, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { faMobile } from "@fortawesome/free-solid-svg-icons";
|
import { faMobile } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { Link, Outlet, useNavigate, useRouter } from "@tanstack/react-router";
|
import { Link, Outlet, useNavigate, useRouter } from "@tanstack/react-router";
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { Mfa } from "@app/components/auth/Mfa";
|
import { Mfa } from "@app/components/auth/Mfa";
|
||||||
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
|
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
|
||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
import { Menu, MenuItem } from "@app/components/v2";
|
import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
||||||
import { useUser } from "@app/context";
|
import { useUser } from "@app/context";
|
||||||
import { usePopUp, useToggle } from "@app/hooks";
|
import { usePopUp, useToggle } from "@app/hooks";
|
||||||
import { useSelectOrganization, workspaceKeys } from "@app/hooks/api";
|
import { useSelectOrganization, workspaceKeys } from "@app/hooks/api";
|
||||||
@@ -18,10 +20,15 @@ import { ProjectType } from "@app/hooks/api/workspace/types";
|
|||||||
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
|
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
|
||||||
|
|
||||||
import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner";
|
import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner";
|
||||||
|
import { MenuIconButton } from "./components/MenuIconButton";
|
||||||
import { SidebarFooter } from "./components/SidebarFooter";
|
import { SidebarFooter } from "./components/SidebarFooter";
|
||||||
import { SidebarHeader } from "./components/SidebarHeader";
|
import { SidebarHeader } from "./components/SidebarHeader";
|
||||||
|
|
||||||
export const OrganizationLayout = () => {
|
type Props = {
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OrganizationLayout = ({ children }: Props) => {
|
||||||
const [shouldShowMfa, toggleShowMfa] = useToggle(false);
|
const [shouldShowMfa, toggleShowMfa] = useToggle(false);
|
||||||
const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL);
|
const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL);
|
||||||
const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {});
|
const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {});
|
||||||
@@ -34,6 +41,9 @@ export const OrganizationLayout = () => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const isNestedLayout = Boolean(children);
|
||||||
|
const [isCollapsed, setIsCollapsed] = useToggle(isNestedLayout);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const handleOrgChange = async (orgId: string) => {
|
const handleOrgChange = async (orgId: string) => {
|
||||||
queryClient.removeQueries({ queryKey: authKeys.getAuthToken });
|
queryClient.removeQueries({ queryKey: authKeys.getAuthToken });
|
||||||
@@ -71,93 +81,171 @@ export const OrganizationLayout = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex">
|
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden transition-all md:flex">
|
||||||
{!window.isSecureContext && <InsecureConnectionBanner />}
|
{!window.isSecureContext && <InsecureConnectionBanner />}
|
||||||
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
|
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
|
||||||
<aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60">
|
<aside
|
||||||
|
className={twMerge(
|
||||||
|
"dark w-60 border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 transition-all duration-150",
|
||||||
|
isCollapsed && "w-16"
|
||||||
|
)}
|
||||||
|
>
|
||||||
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
|
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
|
||||||
<div>
|
<div>
|
||||||
<SidebarHeader onChangeOrg={handleOrgChange} />
|
<SidebarHeader onChangeOrg={handleOrgChange} isCollapsed={isCollapsed} />
|
||||||
<div className="px-1">
|
<div className="px-1">
|
||||||
<Menu className="mt-4">
|
<AnimatePresence mode="wait">
|
||||||
<Link to={`/organization/${ProjectType.SecretManager}/overview` as const}>
|
{isCollapsed ? (
|
||||||
{({ isActive }) => (
|
<motion.div
|
||||||
<MenuItem isSelected={isActive} icon="sliding-carousel">
|
key="menu-icons"
|
||||||
Secret Management
|
className="space-y-1"
|
||||||
</MenuItem>
|
animate={{ x: 0, opacity: 1 }}
|
||||||
)}
|
exit={{ x: -300, opacity: 0 }}
|
||||||
</Link>
|
transition={{ duration: 0.1 }}
|
||||||
<Link to={`/organization/${ProjectType.CertificateManager}/overview` as const}>
|
>
|
||||||
{({ isActive }) => (
|
<Link to={`/organization/${ProjectType.SecretManager}/overview` as const}>
|
||||||
<MenuItem isSelected={isActive} icon="note">
|
<MenuIconButton
|
||||||
Cert Management
|
isSelected={window.location.pathname.startsWith(
|
||||||
</MenuItem>
|
`/${ProjectType.SecretManager}`
|
||||||
)}
|
)}
|
||||||
</Link>
|
icon="sliding-carousel"
|
||||||
<Link to={`/organization/${ProjectType.KMS}/overview` as const}>
|
>
|
||||||
{({ isActive }) => (
|
Secret Manager
|
||||||
<MenuItem isSelected={isActive} icon="unlock">
|
</MenuIconButton>
|
||||||
Key Management
|
</Link>
|
||||||
</MenuItem>
|
<Link
|
||||||
)}
|
to={`/organization/${ProjectType.CertificateManager}/overview` as const}
|
||||||
</Link>
|
>
|
||||||
<Link to={`/organization/${ProjectType.SSH}/overview` as const}>
|
<MenuIconButton
|
||||||
{({ isActive }) => (
|
isSelected={window.location.pathname.startsWith(
|
||||||
<MenuItem isSelected={isActive} icon="verified">
|
`/${ProjectType.CertificateManager}`
|
||||||
SSH
|
)}
|
||||||
</MenuItem>
|
icon="note"
|
||||||
)}
|
>
|
||||||
</Link>
|
Cert Manager
|
||||||
<Link to="/organization/access-management">
|
</MenuIconButton>
|
||||||
{({ isActive }) => (
|
</Link>
|
||||||
<MenuItem isSelected={isActive} icon="groups">
|
<Link to={`/organization/${ProjectType.KMS}/overview` as const}>
|
||||||
Access Control
|
<MenuIconButton
|
||||||
</MenuItem>
|
isSelected={window.location.pathname.startsWith(`/${ProjectType.KMS}`)}
|
||||||
)}
|
icon="unlock"
|
||||||
</Link>
|
>
|
||||||
<Link to="/organization/secret-scanning">
|
KMS
|
||||||
{({ isActive }) => (
|
</MenuIconButton>
|
||||||
<MenuItem isSelected={isActive} icon="secret-scan" className="text-white">
|
</Link>
|
||||||
Secret Scanning
|
<Link to={`/organization/${ProjectType.SSH}/overview` as const}>
|
||||||
</MenuItem>
|
<MenuIconButton
|
||||||
)}
|
isSelected={window.location.pathname.startsWith(`/${ProjectType.SSH}`)}
|
||||||
</Link>
|
icon="verified"
|
||||||
<Link to="/organization/secret-sharing">
|
>
|
||||||
{({ isActive }) => (
|
SSH
|
||||||
<MenuItem isSelected={isActive} icon="lock-closed">
|
</MenuIconButton>
|
||||||
Secret Sharing
|
</Link>
|
||||||
</MenuItem>
|
</motion.div>
|
||||||
)}
|
) : (
|
||||||
</Link>
|
<motion.div
|
||||||
{(window.location.origin.includes("https://app.infisical.com") ||
|
key="menu-list-items"
|
||||||
window.location.origin.includes("https://eu.infisical.com") ||
|
initial={{ x: 300, opacity: 0 }}
|
||||||
window.location.origin.includes("https://gamma.infisical.com")) && (
|
animate={{ x: 0, opacity: 1 }}
|
||||||
<Link to="/organization/billing">
|
exit={{ x: -300, opacity: 0 }}
|
||||||
{({ isActive }) => (
|
transition={{ duration: 0.1 }}
|
||||||
<MenuItem isSelected={isActive} icon="spinning-coin">
|
>
|
||||||
Usage & Billing
|
<Menu>
|
||||||
</MenuItem>
|
<MenuGroup title="PRODUCTS">
|
||||||
)}
|
<Link
|
||||||
</Link>
|
to={`/organization/${ProjectType.SecretManager}/overview` as const}
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="sliding-carousel">
|
||||||
|
Secret Management
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to={
|
||||||
|
`/organization/${ProjectType.CertificateManager}/overview` as const
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="note">
|
||||||
|
Cert Management
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
<Link to={`/organization/${ProjectType.KMS}/overview` as const}>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="unlock">
|
||||||
|
Key Management
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
<Link to={`/organization/${ProjectType.SSH}/overview` as const}>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="verified">
|
||||||
|
SSH
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
<Link to="/organization/secret-scanning">
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem
|
||||||
|
isSelected={isActive}
|
||||||
|
icon="secret-scan"
|
||||||
|
className="text-white"
|
||||||
|
>
|
||||||
|
Secret Scanning
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
</MenuGroup>
|
||||||
|
<MenuGroup title="Others">
|
||||||
|
<Link to="/organization/access-management">
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="groups">
|
||||||
|
Access Control
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
<Link to="/organization/secret-sharing">
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="lock-closed">
|
||||||
|
Secret Sharing
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
{(window.location.origin.includes("https://app.infisical.com") ||
|
||||||
|
window.location.origin.includes("https://eu.infisical.com") ||
|
||||||
|
window.location.origin.includes("https://gamma.infisical.com")) && (
|
||||||
|
<Link to="/organization/billing">
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="spinning-coin">
|
||||||
|
Usage & Billing
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
<Link to="/organization/audit-logs">
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="moving-block">
|
||||||
|
Audit Logs
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
<Link to="/organization/settings">
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="toggle-settings">
|
||||||
|
Organization Settings
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
</MenuGroup>
|
||||||
|
</Menu>
|
||||||
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
<Link to="/organization/audit-logs">
|
</AnimatePresence>
|
||||||
{({ isActive }) => (
|
|
||||||
<MenuItem isSelected={isActive} icon="moving-block">
|
|
||||||
Audit Logs
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
<Link to="/organization/settings">
|
|
||||||
{({ isActive }) => (
|
|
||||||
<MenuItem isSelected={isActive} icon="toggle-settings">
|
|
||||||
Organization Settings
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SidebarFooter />
|
<SidebarFooter isCollapsed={isCollapsed} onToggleSidebar={setIsCollapsed.toggle} />
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
<CreateOrgModal
|
<CreateOrgModal
|
||||||
@@ -165,7 +253,7 @@ export const OrganizationLayout = () => {
|
|||||||
onClose={() => handlePopUpToggle("createOrg", false)}
|
onClose={() => handlePopUpToggle("createOrg", false)}
|
||||||
/>
|
/>
|
||||||
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
|
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
|
||||||
<Outlet />
|
{children || <Outlet />}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { ComponentPropsWithRef, ElementType, useRef } from "react";
|
||||||
|
import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react";
|
||||||
|
|
||||||
|
import { MenuItemProps } from "@app/components/v2";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
export const MenuIconButton = <T extends ElementType = "button">({
|
||||||
|
children,
|
||||||
|
icon,
|
||||||
|
className,
|
||||||
|
isDisabled,
|
||||||
|
isSelected,
|
||||||
|
as: Item = "button",
|
||||||
|
description,
|
||||||
|
// wrapping in forward ref with generic component causes the loss of ts definitions on props
|
||||||
|
inputRef,
|
||||||
|
...props
|
||||||
|
}: MenuItemProps<T> & ComponentPropsWithRef<T>): JSX.Element => {
|
||||||
|
const iconRef = useRef<DotLottie | null>(null);
|
||||||
|
return (
|
||||||
|
<Item
|
||||||
|
type="button"
|
||||||
|
role="menuitem"
|
||||||
|
className={twMerge(
|
||||||
|
"duration-50 group relative flex w-full cursor-pointer flex-col items-center justify-center rounded p-2 font-inter text-sm text-bunker-100 transition-all hover:bg-mineshaft-700",
|
||||||
|
isSelected && "bg-bunker-800 hover:bg-mineshaft-600",
|
||||||
|
isDisabled && "cursor-not-allowed hover:bg-transparent",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
onMouseEnter={() => iconRef.current?.play()}
|
||||||
|
onMouseLeave={() => iconRef.current?.stop()}
|
||||||
|
ref={inputRef}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
isSelected ? "visisble" : "invisible"
|
||||||
|
} absolute -left-[0.28rem] h-full w-[0.07rem] rounded-md bg-primary`}
|
||||||
|
/>
|
||||||
|
{icon && (
|
||||||
|
<div className="my-auto mb-2 h-6 w-6">
|
||||||
|
<DotLottieReact
|
||||||
|
dotLottieRefCallback={(el) => {
|
||||||
|
iconRef.current = el;
|
||||||
|
}}
|
||||||
|
src={`/lotties/${icon}.json`}
|
||||||
|
loop
|
||||||
|
className="h-full w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<span className="flex-grow break-words text-center text-xxs">{children}</span>
|
||||||
|
</Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { MenuIconButton } from "./MenuIconButton";
|
||||||
+146
-25
@@ -1,25 +1,33 @@
|
|||||||
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
|
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||||
import {
|
import {
|
||||||
|
faAnglesLeft,
|
||||||
|
faAnglesRight,
|
||||||
|
faArrowUpRightFromSquare,
|
||||||
faBook,
|
faBook,
|
||||||
faEnvelope,
|
faEnvelope,
|
||||||
faInfinity,
|
faInfinity,
|
||||||
faInfo,
|
faInfo,
|
||||||
|
faInfoCircle,
|
||||||
faPlus,
|
faPlus,
|
||||||
faQuestion
|
faQuestion,
|
||||||
|
faUser
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { Link } from "@tanstack/react-router";
|
import { Link, useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { WishForm } from "@app/components/features/WishForm";
|
import { WishForm } from "@app/components/features/WishForm";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger
|
DropdownMenuTrigger,
|
||||||
|
MenuItem
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { envConfig } from "@app/config/env";
|
import { envConfig } from "@app/config/env";
|
||||||
import { useOrganization, useSubscription } from "@app/context";
|
import { useOrganization, useSubscription, useUser } from "@app/context";
|
||||||
import { useGetOrgTrialUrl } from "@app/hooks/api";
|
import { useGetOrgTrialUrl, useLogoutUser } from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { MenuIconButton } from "../MenuIconButton";
|
||||||
|
|
||||||
export const INFISICAL_SUPPORT_OPTIONS = [
|
export const INFISICAL_SUPPORT_OPTIONS = [
|
||||||
[
|
[
|
||||||
@@ -44,40 +52,68 @@ export const INFISICAL_SUPPORT_OPTIONS = [
|
|||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
export const SidebarFooter = () => {
|
type Props = {
|
||||||
|
isCollapsed?: boolean;
|
||||||
|
onToggleSidebar: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SidebarFooter = ({ isCollapsed, onToggleSidebar }: Props) => {
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
|
|
||||||
const { mutateAsync } = useGetOrgTrialUrl();
|
const { mutateAsync } = useGetOrgTrialUrl();
|
||||||
|
|
||||||
|
const { user } = useUser();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const logout = useLogoutUser();
|
||||||
|
const logOutUser = async () => {
|
||||||
|
try {
|
||||||
|
console.log("Logging out...");
|
||||||
|
await logout.mutateAsync();
|
||||||
|
navigate({ to: "/login" });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`relative mt-10 ${
|
className={`relative mt-10 ${
|
||||||
subscription && subscription.slug === "starter" && !subscription.has_used_trial
|
subscription && subscription.slug === "starter" && !subscription.has_used_trial
|
||||||
? "mb-2"
|
? "mb-2"
|
||||||
: "mb-4"
|
: "mb-4"
|
||||||
} flex w-full cursor-default flex-col items-center px-3 text-sm text-mineshaft-400`}
|
} flex w-full cursor-default flex-col items-center ${isCollapsed ? "px-1" : "px-2"} text-sm text-mineshaft-400`}
|
||||||
>
|
>
|
||||||
{(window.location.origin.includes("https://app.infisical.com") ||
|
{(window.location.origin.includes("https://app.infisical.com") ||
|
||||||
window.location.origin.includes("https://gamma.infisical.com")) && <WishForm />}
|
window.location.origin.includes("https://gamma.infisical.com")) &&
|
||||||
<Link
|
!isCollapsed && <WishForm />}
|
||||||
to="/organization/access-management"
|
{!isCollapsed && (
|
||||||
search={{
|
<Link
|
||||||
action: "invite"
|
to="/organization/access-management"
|
||||||
}}
|
search={{
|
||||||
className="w-full"
|
action: "invite"
|
||||||
>
|
}}
|
||||||
<div className="mb-3 w-full pl-5 duration-200 hover:text-mineshaft-200">
|
className="w-full"
|
||||||
<FontAwesomeIcon icon={faPlus} className="mr-3" />
|
>
|
||||||
Invite people
|
<div className="mb-3 w-full pl-2 duration-200 hover:text-mineshaft-200">
|
||||||
</div>
|
<FontAwesomeIcon icon={faPlus} className="mr-3" />
|
||||||
</Link>
|
Invite people
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
|
|
||||||
<FontAwesomeIcon icon={faQuestion} className="mr-3 px-[0.1rem]" />
|
|
||||||
Help & Support
|
|
||||||
</div>
|
</div>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger className="w-full">
|
||||||
|
{isCollapsed ? (
|
||||||
|
<MenuIconButton>
|
||||||
|
<FontAwesomeIcon icon={faInfoCircle} className="mb-3 text-lg" />
|
||||||
|
Support
|
||||||
|
</MenuIconButton>
|
||||||
|
) : (
|
||||||
|
<div className="mb-4 flex w-full items-center pl-2 duration-200 hover:text-mineshaft-200">
|
||||||
|
<FontAwesomeIcon icon={faQuestion} className="mr-3 px-[0.1rem]" />
|
||||||
|
Help & Support
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => (
|
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => (
|
||||||
@@ -125,6 +161,91 @@ export const SidebarFooter = () => {
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{isCollapsed ? (
|
||||||
|
<MenuIconButton onClick={onToggleSidebar} className="p-4">
|
||||||
|
<FontAwesomeIcon icon={faAnglesRight} className="text-lg" />
|
||||||
|
</MenuIconButton>
|
||||||
|
) : (
|
||||||
|
<MenuItem onClick={onToggleSidebar} className="mb-2 w-full px-2 text-mineshaft-400">
|
||||||
|
<FontAwesomeIcon icon={faAnglesLeft} className="mr-3" />
|
||||||
|
Collapse
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger className="w-full">
|
||||||
|
{isCollapsed ? (
|
||||||
|
<div>
|
||||||
|
<MenuIconButton>
|
||||||
|
<div className="my-1 flex h-6 w-6 items-center justify-center rounded-md bg-primary text-sm uppercase text-black">
|
||||||
|
{user?.firstName?.charAt(0)}
|
||||||
|
</div>
|
||||||
|
</MenuIconButton>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex w-full cursor-pointer items-center rounded-md border border-mineshaft-600 p-2 px-1">
|
||||||
|
<div className="mr-2 flex h-6 w-6 items-center justify-center rounded-md bg-primary text-sm uppercase">
|
||||||
|
{user?.firstName?.charAt(0)}
|
||||||
|
</div>
|
||||||
|
<div className="max-w-40 flex-grow truncate text-ellipsis text-left text-sm capitalize text-white">
|
||||||
|
{user?.firstName} {user?.lastName}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<FontAwesomeIcon icon={faUser} className="text-xs text-mineshaft-400" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
|
<div className="px-2 py-1 text-xs text-mineshaft-400">{user?.username}</div>
|
||||||
|
<Link to="/personal-settings">
|
||||||
|
<DropdownMenuItem>Personal Settings</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/docs/documentation/getting-started/introduction"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
Documentation
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.06rem] pl-1.5 text-xxs"
|
||||||
|
/>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://infisical.com/slack"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
Join Slack Community
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faArrowUpRightFromSquare}
|
||||||
|
className="mb-[0.06rem] pl-1.5 text-xxs"
|
||||||
|
/>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</a>
|
||||||
|
{user?.superAdmin && (
|
||||||
|
<Link to="/admin">
|
||||||
|
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
|
||||||
|
Server Admin Console
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
<Link to="/organization/admin">
|
||||||
|
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
|
||||||
|
Organization Admin Console
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
<div className="mt-1 h-1 border-t border-mineshaft-600" />
|
||||||
|
<button type="button" onClick={logOutUser} className="w-full">
|
||||||
|
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
||||||
|
</button>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+166
-84
@@ -1,6 +1,6 @@
|
|||||||
import { faAngleDown, faArrowUpRightFromSquare, faCheck } from "@fortawesome/free-solid-svg-icons";
|
import { faCheck, faSort } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { Link, useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -9,17 +9,18 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger
|
DropdownMenuTrigger
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useOrganization, useUser } from "@app/context";
|
import { useOrganization } from "@app/context";
|
||||||
import { useGetOrganizations, useLogoutUser } from "@app/hooks/api";
|
import { useGetOrganizations, useLogoutUser } from "@app/hooks/api";
|
||||||
import { AuthMethod } from "@app/hooks/api/users/types";
|
import { AuthMethod } from "@app/hooks/api/users/types";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
type Prop = {
|
type Prop = {
|
||||||
onChangeOrg: (orgId: string) => void;
|
onChangeOrg: (orgId: string) => void;
|
||||||
|
isCollapsed?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SidebarHeader = ({ onChangeOrg }: Prop) => {
|
export const SidebarHeader = ({ onChangeOrg, isCollapsed }: Prop) => {
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { user } = useUser();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data: orgs } = useGetOrganizations();
|
const { data: orgs } = useGetOrganizations();
|
||||||
|
|
||||||
@@ -35,24 +36,42 @@ export const SidebarHeader = ({ onChangeOrg }: Prop) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-12 cursor-default items-center px-3 pt-6">
|
<div
|
||||||
|
className={twMerge(
|
||||||
|
"flex cursor-pointer items-center p-2 pt-4",
|
||||||
|
isCollapsed && "transition-all duration-150 hover:bg-mineshaft-700"
|
||||||
|
)}
|
||||||
|
>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild className="max-w-[160px] data-[state=open]:bg-mineshaft-600">
|
<DropdownMenuTrigger asChild>
|
||||||
<div className="mr-auto flex items-center rounded-md py-1.5 pl-1.5 pr-2 hover:bg-mineshaft-600">
|
<div
|
||||||
<div className="flex h-5 w-5 min-w-[20px] items-center justify-center rounded-md bg-primary text-sm">
|
className={twMerge(
|
||||||
{currentOrg?.name.charAt(0)}
|
"flex w-full items-center justify-center rounded-md border border-mineshaft-600 p-1 transition-all",
|
||||||
</div>
|
isCollapsed ? "border-none" : "transition-all duration-150 hover:bg-mineshaft-700"
|
||||||
<div
|
)}
|
||||||
className="overflow-hidden truncate text-ellipsis pl-2 text-sm text-mineshaft-100"
|
>
|
||||||
style={{ maxWidth: "140px" }}
|
{isCollapsed ? (
|
||||||
>
|
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-primary">
|
||||||
{currentOrg?.name}
|
{currentOrg?.name.charAt(0)}
|
||||||
</div>
|
</div>
|
||||||
<FontAwesomeIcon icon={faAngleDown} className="pl-1 pt-1 text-xs text-mineshaft-300" />
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="mr-2 flex h-8 w-8 items-center justify-center rounded-md bg-primary">
|
||||||
|
{currentOrg?.name.charAt(0)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-grow flex-col text-white">
|
||||||
|
<div className="max-w-36 truncate text-ellipsis text-sm font-medium capitalize">
|
||||||
|
{currentOrg?.name}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-mineshaft-400">Free Plan</div>
|
||||||
|
</div>
|
||||||
|
<FontAwesomeIcon icon={faSort} className="text-xs text-mineshaft-400" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
<div className="px-2 py-1 text-xs text-mineshaft-400">{user?.username}</div>
|
<div className="px-2 py-1 text-xs capitalize text-mineshaft-400">organizations</div>
|
||||||
{orgs?.map((org) => {
|
{orgs?.map((org) => {
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem key={org.id}>
|
<DropdownMenuItem key={org.id}>
|
||||||
@@ -93,71 +112,6 @@ export const SidebarHeader = ({ onChangeOrg }: Prop) => {
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<div className="mt-1 h-1 border-t border-mineshaft-600" />
|
|
||||||
<button type="button" onClick={logOutUser} className="w-full">
|
|
||||||
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
|
||||||
</button>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger
|
|
||||||
asChild
|
|
||||||
className="p-1 hover:bg-primary-400 hover:text-black data-[state=open]:bg-primary-400 data-[state=open]:text-black"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="child flex items-center justify-center rounded-full bg-mineshaft pr-1 text-mineshaft-300 hover:bg-mineshaft-500"
|
|
||||||
style={{ fontSize: "11px", width: "26px", height: "26px" }}
|
|
||||||
>
|
|
||||||
{user?.firstName?.charAt(0)}
|
|
||||||
{user?.lastName && user?.lastName?.charAt(0)}
|
|
||||||
</div>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
|
||||||
<div className="px-2 py-1 text-xs text-mineshaft-400">{user?.username}</div>
|
|
||||||
<Link to="/personal-settings">
|
|
||||||
<DropdownMenuItem>Personal Settings</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
<a
|
|
||||||
href="https://infisical.com/docs/documentation/getting-started/introduction"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
|
|
||||||
>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
Documentation
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faArrowUpRightFromSquare}
|
|
||||||
className="mb-[0.06rem] pl-1.5 text-xxs"
|
|
||||||
/>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://infisical.com/slack"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
|
|
||||||
>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
Join Slack Community
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faArrowUpRightFromSquare}
|
|
||||||
className="mb-[0.06rem] pl-1.5 text-xxs"
|
|
||||||
/>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</a>
|
|
||||||
{user?.superAdmin && (
|
|
||||||
<Link to="/admin">
|
|
||||||
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
|
|
||||||
Server Admin Console
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
<Link to="/organization/admin">
|
|
||||||
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
|
|
||||||
Organization Admin Console
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Link>
|
|
||||||
<div className="mt-1 h-1 border-t border-mineshaft-600" />
|
<div className="mt-1 h-1 border-t border-mineshaft-600" />
|
||||||
<button type="button" onClick={logOutUser} className="w-full">
|
<button type="button" onClick={logOutUser} className="w-full">
|
||||||
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
||||||
@@ -167,3 +121,131 @@ export const SidebarHeader = ({ onChangeOrg }: Prop) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// <DropdownMenu>
|
||||||
|
// <DropdownMenuTrigger asChild className="max-w-[160px] data-[state=open]:bg-mineshaft-600">
|
||||||
|
// <div className="mr-auto flex items-center rounded-md py-1.5 pl-1.5 pr-2 hover:bg-mineshaft-600">
|
||||||
|
// <div className="flex h-5 w-5 min-w-[20px] items-center justify-center rounded-md bg-primary text-sm">
|
||||||
|
// {currentOrg?.name.charAt(0)}
|
||||||
|
// </div>
|
||||||
|
// <div
|
||||||
|
// className="overflow-hidden truncate text-ellipsis pl-2 text-sm text-mineshaft-100"
|
||||||
|
// style={{ maxWidth: "140px" }}
|
||||||
|
// >
|
||||||
|
// {currentOrg?.name}
|
||||||
|
// </div>
|
||||||
|
// <FontAwesomeIcon icon={faAngleDown} className="pl-1 pt-1 text-xs text-mineshaft-300" />
|
||||||
|
// </div>
|
||||||
|
// </DropdownMenuTrigger>
|
||||||
|
// <DropdownMenuContent align="start" className="p-1">
|
||||||
|
// <div className="px-2 py-1 text-xs text-mineshaft-400">{user?.username}</div>
|
||||||
|
// {orgs?.map((org) => {
|
||||||
|
// return (
|
||||||
|
// <DropdownMenuItem key={org.id}>
|
||||||
|
// <Button
|
||||||
|
// onClick={async () => {
|
||||||
|
// if (currentOrg?.id === org.id) return;
|
||||||
|
//
|
||||||
|
// if (org.authEnforced) {
|
||||||
|
// // org has an org-level auth method enabled (e.g. SAML)
|
||||||
|
// // -> logout + redirect to SAML SSO
|
||||||
|
//
|
||||||
|
// await logout.mutateAsync();
|
||||||
|
// if (org.orgAuthMethod === AuthMethod.OIDC) {
|
||||||
|
// window.open(`/api/v1/sso/oidc/login?orgSlug=${org.slug}`);
|
||||||
|
// } else {
|
||||||
|
// window.open(`/api/v1/sso/redirect/saml2/organizations/${org.slug}`);
|
||||||
|
// }
|
||||||
|
// window.close();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// onChangeOrg(org?.id);
|
||||||
|
// }}
|
||||||
|
// variant="plain"
|
||||||
|
// colorSchema="secondary"
|
||||||
|
// size="xs"
|
||||||
|
// className="flex w-full items-center justify-start p-0 font-normal"
|
||||||
|
// leftIcon={
|
||||||
|
// currentOrg?.id === org.id && (
|
||||||
|
// <FontAwesomeIcon icon={faCheck} className="mr-3 text-primary" />
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// >
|
||||||
|
// <div className="flex w-full max-w-[150px] items-center justify-between truncate">
|
||||||
|
// {org.name}
|
||||||
|
// </div>
|
||||||
|
// </Button>
|
||||||
|
// </DropdownMenuItem>
|
||||||
|
// );
|
||||||
|
// })}
|
||||||
|
// <div className="mt-1 h-1 border-t border-mineshaft-600" />
|
||||||
|
// <button type="button" onClick={logOutUser} className="w-full">
|
||||||
|
// <DropdownMenuItem>Log Out</DropdownMenuItem>
|
||||||
|
// </button>
|
||||||
|
// </DropdownMenuContent>
|
||||||
|
// </DropdownMenu>
|
||||||
|
// <DropdownMenu>
|
||||||
|
// <DropdownMenuTrigger
|
||||||
|
// asChild
|
||||||
|
// className="p-1 hover:bg-primary-400 hover:text-black data-[state=open]:bg-primary-400 data-[state=open]:text-black"
|
||||||
|
// >
|
||||||
|
// <div
|
||||||
|
// className="child flex items-center justify-center rounded-full bg-mineshaft pr-1 text-mineshaft-300 hover:bg-mineshaft-500"
|
||||||
|
// style={{ fontSize: "11px", width: "26px", height: "26px" }}
|
||||||
|
// >
|
||||||
|
// {user?.firstName?.charAt(0)}
|
||||||
|
// {user?.lastName && user?.lastName?.charAt(0)}
|
||||||
|
// </div>
|
||||||
|
// </DropdownMenuTrigger>
|
||||||
|
// <DropdownMenuContent align="start" className="p-1">
|
||||||
|
// <div className="px-2 py-1 text-xs text-mineshaft-400">{user?.username}</div>
|
||||||
|
// <Link to="/personal-settings">
|
||||||
|
// <DropdownMenuItem>Personal Settings</DropdownMenuItem>
|
||||||
|
// </Link>
|
||||||
|
// <a
|
||||||
|
// href="https://infisical.com/docs/documentation/getting-started/introduction"
|
||||||
|
// target="_blank"
|
||||||
|
// rel="noopener noreferrer"
|
||||||
|
// className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
|
||||||
|
// >
|
||||||
|
// <DropdownMenuItem>
|
||||||
|
// Documentation
|
||||||
|
// <FontAwesomeIcon
|
||||||
|
// icon={faArrowUpRightFromSquare}
|
||||||
|
// className="mb-[0.06rem] pl-1.5 text-xxs"
|
||||||
|
// />
|
||||||
|
// </DropdownMenuItem>
|
||||||
|
// </a>
|
||||||
|
// <a
|
||||||
|
// href="https://infisical.com/slack"
|
||||||
|
// target="_blank"
|
||||||
|
// rel="noopener noreferrer"
|
||||||
|
// className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
|
||||||
|
// >
|
||||||
|
// <DropdownMenuItem>
|
||||||
|
// Join Slack Community
|
||||||
|
// <FontAwesomeIcon
|
||||||
|
// icon={faArrowUpRightFromSquare}
|
||||||
|
// className="mb-[0.06rem] pl-1.5 text-xxs"
|
||||||
|
// />
|
||||||
|
// </DropdownMenuItem>
|
||||||
|
// </a>
|
||||||
|
// {user?.superAdmin && (
|
||||||
|
// <Link to="/admin">
|
||||||
|
// <DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
|
||||||
|
// Server Admin Console
|
||||||
|
// </DropdownMenuItem>
|
||||||
|
// </Link>
|
||||||
|
// )}
|
||||||
|
// <Link to="/organization/admin">
|
||||||
|
// <DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
|
||||||
|
// Organization Admin Console
|
||||||
|
// </DropdownMenuItem>
|
||||||
|
// </Link>
|
||||||
|
// <div className="mt-1 h-1 border-t border-mineshaft-600" />
|
||||||
|
// <button type="button" onClick={logOutUser} className="w-full">
|
||||||
|
// <DropdownMenuItem>Log Out</DropdownMenuItem>
|
||||||
|
// </button>
|
||||||
|
// </DropdownMenuContent>
|
||||||
|
// </DropdownMenu>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Link, Outlet, useNavigate, useRouter } from "@tanstack/react-router";
|
|||||||
|
|
||||||
import { Mfa } from "@app/components/auth/Mfa";
|
import { Mfa } from "@app/components/auth/Mfa";
|
||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
import { Menu, MenuItem } from "@app/components/v2";
|
import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
||||||
import { useUser, useWorkspace } from "@app/context";
|
import { useUser, useWorkspace } from "@app/context";
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
@@ -22,7 +22,6 @@ import { ProjectType } from "@app/hooks/api/workspace/types";
|
|||||||
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
|
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
|
||||||
|
|
||||||
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
|
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
|
||||||
import { SidebarFooter } from "../OrganizationLayout/components/SidebarFooter";
|
|
||||||
import { ProjectSelect } from "./components/ProjectSelect";
|
import { ProjectSelect } from "./components/ProjectSelect";
|
||||||
import { SidebarHeader } from "./components/SidebarHeader";
|
import { SidebarHeader } from "./components/SidebarHeader";
|
||||||
|
|
||||||
@@ -105,144 +104,146 @@ export const ProjectLayout = () => {
|
|||||||
<aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60">
|
<aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60">
|
||||||
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
|
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
|
||||||
<div>
|
<div>
|
||||||
<SidebarHeader onChangeOrg={handleOrgChange} />
|
|
||||||
<ProjectSelect />
|
<ProjectSelect />
|
||||||
<div className="px-1">
|
<div className="px-1">
|
||||||
<Menu>
|
<Menu>
|
||||||
{isSecretManager && (
|
<MenuGroup title="Main Menu">
|
||||||
<Link
|
{isSecretManager && (
|
||||||
to={`/${ProjectType.SecretManager}/$projectId/overview` as const}
|
<Link
|
||||||
params={{
|
to={`/${ProjectType.SecretManager}/$projectId/overview` as const}
|
||||||
projectId: currentWorkspace.id
|
params={{
|
||||||
}}
|
projectId: currentWorkspace.id
|
||||||
>
|
}}
|
||||||
{({ isActive }) => (
|
>
|
||||||
<MenuItem isSelected={isActive} icon="lock-closed">
|
{({ isActive }) => (
|
||||||
{t("nav.menu.secrets")}
|
<MenuItem isSelected={isActive} icon="lock-closed">
|
||||||
</MenuItem>
|
{t("nav.menu.secrets")}
|
||||||
)}
|
</MenuItem>
|
||||||
</Link>
|
)}
|
||||||
)}
|
</Link>
|
||||||
{isCertManager && (
|
|
||||||
<Link
|
|
||||||
to={`/${ProjectType.CertificateManager}/$projectId/overview` as const}
|
|
||||||
params={{
|
|
||||||
projectId: currentWorkspace.id
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ isActive }) => (
|
|
||||||
<MenuItem isSelected={isActive} icon="lock-closed">
|
|
||||||
Overview
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
{isCmek && (
|
|
||||||
<Link
|
|
||||||
to={`/${ProjectType.KMS}/$projectId/overview` as const}
|
|
||||||
params={{
|
|
||||||
projectId: currentWorkspace.id
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ isActive }) => (
|
|
||||||
<MenuItem isSelected={isActive} icon="lock-closed">
|
|
||||||
Overview
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
{isSSH && (
|
|
||||||
<Link
|
|
||||||
to={`/${ProjectType.SSH}/$projectId/overview` as const}
|
|
||||||
params={{
|
|
||||||
projectId: currentWorkspace.id
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ isActive }) => (
|
|
||||||
<MenuItem isSelected={isActive} icon="lock-closed">
|
|
||||||
Overview
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
<Link
|
|
||||||
to={`/${currentWorkspace.type}/$projectId/access-management` as const}
|
|
||||||
params={{
|
|
||||||
projectId: currentWorkspace.id
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ isActive }) => (
|
|
||||||
<MenuItem isSelected={isActive} icon="groups">
|
|
||||||
Access Control
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
)}
|
||||||
</Link>
|
{isCertManager && (
|
||||||
{isSecretManager && (
|
<Link
|
||||||
<Link
|
to={`/${ProjectType.CertificateManager}/$projectId/overview` as const}
|
||||||
to={`/${ProjectType.SecretManager}/$projectId/integrations` as const}
|
params={{
|
||||||
params={{
|
projectId: currentWorkspace.id
|
||||||
projectId: currentWorkspace.id
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{({ isActive }) => (
|
||||||
{({ isActive }) => (
|
<MenuItem isSelected={isActive} icon="lock-closed">
|
||||||
<MenuItem isSelected={isActive} icon="jigsaw-puzzle">
|
Overview
|
||||||
{t("nav.menu.integrations")}
|
</MenuItem>
|
||||||
</MenuItem>
|
)}
|
||||||
)}
|
</Link>
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
{isSecretManager && (
|
|
||||||
<Link
|
|
||||||
to={`/${ProjectType.SecretManager}/$projectId/secret-rotation` as const}
|
|
||||||
params={{
|
|
||||||
projectId: currentWorkspace.id
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ isActive }) => (
|
|
||||||
<MenuItem isSelected={isActive} icon="rotation">
|
|
||||||
Secret Rotation
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
{isSecretManager && (
|
|
||||||
<Link
|
|
||||||
to={`/${ProjectType.SecretManager}/$projectId/approval` as const}
|
|
||||||
params={{
|
|
||||||
projectId: currentWorkspace.id
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ isActive }) => (
|
|
||||||
<MenuItem isSelected={isActive} icon="circular-check">
|
|
||||||
Approvals
|
|
||||||
{Boolean(
|
|
||||||
secretApprovalReqCount?.open ||
|
|
||||||
accessApprovalRequestCount?.pendingCount
|
|
||||||
) && (
|
|
||||||
<span className="ml-2 rounded border border-primary-400 bg-primary-600 px-1 py-0.5 text-xs font-semibold text-black">
|
|
||||||
{pendingRequestsCount}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
<Link
|
|
||||||
to={`/${currentWorkspace.type}/$projectId/settings` as const}
|
|
||||||
params={{
|
|
||||||
projectId: currentWorkspace.id
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ isActive }) => (
|
|
||||||
<MenuItem isSelected={isActive} icon="toggle-settings">
|
|
||||||
{t("nav.menu.project-settings")}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
)}
|
||||||
</Link>
|
{isCmek && (
|
||||||
|
<Link
|
||||||
|
to={`/${ProjectType.KMS}/$projectId/overview` as const}
|
||||||
|
params={{
|
||||||
|
projectId: currentWorkspace.id
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="lock-closed">
|
||||||
|
Overview
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
{isSSH && (
|
||||||
|
<Link
|
||||||
|
to={`/${ProjectType.SSH}/$projectId/overview` as const}
|
||||||
|
params={{
|
||||||
|
projectId: currentWorkspace.id
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="lock-closed">
|
||||||
|
Overview
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
{isSecretManager && (
|
||||||
|
<Link
|
||||||
|
to={`/${ProjectType.SecretManager}/$projectId/integrations` as const}
|
||||||
|
params={{
|
||||||
|
projectId: currentWorkspace.id
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="jigsaw-puzzle">
|
||||||
|
{t("nav.menu.integrations")}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
{isSecretManager && (
|
||||||
|
<Link
|
||||||
|
to={`/${ProjectType.SecretManager}/$projectId/secret-rotation` as const}
|
||||||
|
params={{
|
||||||
|
projectId: currentWorkspace.id
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="rotation">
|
||||||
|
Secret Rotation
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
{isSecretManager && (
|
||||||
|
<Link
|
||||||
|
to={`/${ProjectType.SecretManager}/$projectId/approval` as const}
|
||||||
|
params={{
|
||||||
|
projectId: currentWorkspace.id
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="circular-check">
|
||||||
|
Approvals
|
||||||
|
{Boolean(
|
||||||
|
secretApprovalReqCount?.open ||
|
||||||
|
accessApprovalRequestCount?.pendingCount
|
||||||
|
) && (
|
||||||
|
<span className="ml-2 rounded border border-primary-400 bg-primary-600 px-1 py-0.5 text-xs font-semibold text-black">
|
||||||
|
{pendingRequestsCount}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</MenuGroup>
|
||||||
|
<MenuGroup title="Others">
|
||||||
|
<Link
|
||||||
|
to={`/${currentWorkspace.type}/$projectId/access-management` as const}
|
||||||
|
params={{
|
||||||
|
projectId: currentWorkspace.id
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="groups">
|
||||||
|
Access Control
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to={`/${currentWorkspace.type}/$projectId/settings` as const}
|
||||||
|
params={{
|
||||||
|
projectId: currentWorkspace.id
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<MenuItem isSelected={isActive} icon="toggle-settings">
|
||||||
|
{t("nav.menu.project-settings")}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
</MenuGroup>
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SidebarFooter />
|
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
|
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export const ProjectSelect = () => {
|
|||||||
}, [workspaces, projectFavorites, currentWorkspace]);
|
}, [workspaces, projectFavorites, currentWorkspace]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-4 mt-5 w-full p-3">
|
<div className="mt-2 w-full p-3">
|
||||||
<p className="mb-1 ml-1.5 text-xs font-semibold uppercase text-gray-400">
|
<p className="mb-1 ml-1.5 text-xs font-semibold uppercase text-gray-400">
|
||||||
{currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"}
|
{currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -4,11 +4,16 @@ import { workspaceKeys } from "@app/hooks/api";
|
|||||||
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||||
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
||||||
|
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
|
||||||
)({
|
)({
|
||||||
component: ProjectLayout,
|
component: () => (
|
||||||
|
<OrganizationLayout>
|
||||||
|
<ProjectLayout />
|
||||||
|
</OrganizationLayout>
|
||||||
|
),
|
||||||
beforeLoad: async ({ params, context }) => {
|
beforeLoad: async ({ params, context }) => {
|
||||||
await context.queryClient.ensureQueryData({
|
await context.queryClient.ensureQueryData({
|
||||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||||
|
|||||||
@@ -3,12 +3,17 @@ import { createFileRoute } from "@tanstack/react-router";
|
|||||||
import { workspaceKeys } from "@app/hooks/api";
|
import { workspaceKeys } from "@app/hooks/api";
|
||||||
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||||
|
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
|
||||||
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout"
|
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout"
|
||||||
)({
|
)({
|
||||||
component: ProjectLayout,
|
component: () => (
|
||||||
|
<OrganizationLayout>
|
||||||
|
<ProjectLayout />
|
||||||
|
</OrganizationLayout>
|
||||||
|
),
|
||||||
beforeLoad: async ({ params, context }) => {
|
beforeLoad: async ({ params, context }) => {
|
||||||
await context.queryClient.ensureQueryData({
|
await context.queryClient.ensureQueryData({
|
||||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { CertManagerOverviewPage } from "./CertManagerOverviewPage";
|
import { CertManagerOverviewPage } from "./CertManagerOverviewPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/cert-manager/overview"
|
"/_authenticate/_inject-org-details/organization/_layout/cert-manager/overview"
|
||||||
)({
|
)({
|
||||||
component: CertManagerOverviewPage
|
component: CertManagerOverviewPage,
|
||||||
|
context: () => ({
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "products",
|
||||||
|
link: linkOptions({ disabled: true, to: "/" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Cert Managers",
|
||||||
|
link: linkOptions({ to: "/organization/cert-manager/overview" })
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { KmsOverviewPage } from "./KmsOverviewPage";
|
import { KmsOverviewPage } from "./KmsOverviewPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/kms/overview"
|
"/_authenticate/_inject-org-details/organization/_layout/kms/overview"
|
||||||
)({
|
)({
|
||||||
component: KmsOverviewPage
|
component: KmsOverviewPage,
|
||||||
|
context: () => ({
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "products",
|
||||||
|
link: linkOptions({ disabled: true, to: "/" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "KMS",
|
||||||
|
link: linkOptions({ to: "/organization/kms/overview" })
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { SecretManagerOverviewPage } from "./SecretManagerOverviewPage";
|
import { SecretManagerOverviewPage } from "./SecretManagerOverviewPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/secret-manager/overview"
|
"/_authenticate/_inject-org-details/organization/_layout/secret-manager/overview"
|
||||||
)({
|
)({
|
||||||
component: SecretManagerOverviewPage
|
component: SecretManagerOverviewPage,
|
||||||
|
context: () => ({
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "products",
|
||||||
|
link: linkOptions({ disabled: true, to: "/" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Secret Managers",
|
||||||
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { SshOverviewPage } from "./SshOverviewPage";
|
import { SshOverviewPage } from "./SshOverviewPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/ssh/overview"
|
"/_authenticate/_inject-org-details/organization/_layout/ssh/overview"
|
||||||
)({
|
)({
|
||||||
component: SshOverviewPage
|
component: SshOverviewPage,
|
||||||
|
context: () => ({
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "products",
|
||||||
|
link: linkOptions({ disabled: true, to: "/" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "SSH",
|
||||||
|
link: linkOptions({ to: "/organization/ssh/overview" })
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,11 +4,16 @@ import { workspaceKeys } from "@app/hooks/api";
|
|||||||
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||||
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
||||||
|
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
||||||
)({
|
)({
|
||||||
component: ProjectLayout,
|
component: () => (
|
||||||
|
<OrganizationLayout>
|
||||||
|
<ProjectLayout />
|
||||||
|
</OrganizationLayout>
|
||||||
|
),
|
||||||
beforeLoad: async ({ params, context }) => {
|
beforeLoad: async ({ params, context }) => {
|
||||||
await context.queryClient.ensureQueryData({
|
await context.queryClient.ensureQueryData({
|
||||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||||
|
|||||||
@@ -4,11 +4,16 @@ import { workspaceKeys } from "@app/hooks/api";
|
|||||||
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||||
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
||||||
|
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
||||||
)({
|
)({
|
||||||
component: ProjectLayout,
|
component: () => (
|
||||||
|
<OrganizationLayout>
|
||||||
|
<ProjectLayout />
|
||||||
|
</OrganizationLayout>
|
||||||
|
),
|
||||||
beforeLoad: async ({ params, context }) => {
|
beforeLoad: async ({ params, context }) => {
|
||||||
await context.queryClient.ensureQueryData({
|
await context.queryClient.ensureQueryData({
|
||||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||||
|
|||||||
Reference in New Issue
Block a user