mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 22:26:15 +00:00
feat: completed all nav restructing and breadcrumbs cleanup
This commit is contained in:
@@ -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 (
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
{isLastItem ? (
|
||||||
|
<div className="group flex items-center space-x-2">
|
||||||
|
<span
|
||||||
|
className={twMerge(
|
||||||
|
"text-sm font-semibold transition-all",
|
||||||
|
isCopying ? "text-bunker-200" : "text-bunker-300"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{folderName}
|
||||||
|
</span>
|
||||||
|
<Tooltip className="relative right-2" position="bottom" content="Copy secret path">
|
||||||
|
<IconButton
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="copy"
|
||||||
|
onClick={() => {
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={!isCopying ? faCopy : faCheck}
|
||||||
|
size="sm"
|
||||||
|
className="cursor-pointer"
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
to={`/${ProjectType.SecretManager}/$projectId/secrets/$envSlug` as const}
|
||||||
|
params={{
|
||||||
|
projectId,
|
||||||
|
envSlug: environmentSlug
|
||||||
|
}}
|
||||||
|
search={(query) => ({ ...query, secretPath: newSecretPath })}
|
||||||
|
className={twMerge(
|
||||||
|
"text-sm font-semibold transition-all hover:text-primary",
|
||||||
|
isCopying && "text-primary"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{folderName}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,8 +1,19 @@
|
|||||||
import * as React from "react";
|
/* eslint-disable react/prop-types */
|
||||||
import { faChevronRight, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
import React from "react";
|
||||||
|
import { faCaretDown, faChevronRight, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
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 { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuTrigger
|
||||||
|
} from "../Dropdown";
|
||||||
|
|
||||||
const Breadcrumb = React.forwardRef<
|
const Breadcrumb = React.forwardRef<
|
||||||
HTMLElement,
|
HTMLElement,
|
||||||
React.ComponentPropsWithoutRef<"nav"> & {
|
React.ComponentPropsWithoutRef<"nav"> & {
|
||||||
@@ -27,21 +38,25 @@ BreadcrumbList.displayName = "BreadcrumbList";
|
|||||||
|
|
||||||
const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<"li">>(
|
const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<"li">>(
|
||||||
({ className, ...props }, ref) => (
|
({ className, ...props }, ref) => (
|
||||||
<li ref={ref} className={twMerge("inline-flex items-center gap-1.5", className)} {...props} />
|
<li
|
||||||
|
ref={ref}
|
||||||
|
className={twMerge("inline-flex items-center gap-1.5 font-medium", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
BreadcrumbItem.displayName = "BreadcrumbItem";
|
BreadcrumbItem.displayName = "BreadcrumbItem";
|
||||||
|
|
||||||
const BreadcrumbLink = React.forwardRef<
|
const BreadcrumbLink = React.forwardRef<
|
||||||
HTMLLIElement,
|
HTMLDivElement,
|
||||||
React.ComponentPropsWithoutRef<"li"> & {
|
React.ComponentPropsWithoutRef<"div"> & {
|
||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
}
|
}
|
||||||
>(({ asChild, className, ...props }, ref) => {
|
>(({ asChild, className, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<li
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={twMerge("transition-colors hover:text-bunker-200", className)}
|
className={twMerge("transition-colors hover:text-primary-400", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -87,12 +102,116 @@ const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<"span"
|
|||||||
);
|
);
|
||||||
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
|
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[] }) => (
|
||||||
|
<div className="mx-auto max-w-7xl py-4 capitalize text-white">
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbList>
|
||||||
|
{(breadcrumbs as TBreadcrumbFormat[]).map((el, index) => {
|
||||||
|
const isNotLastCrumb = index + 1 !== breadcrumbs.length;
|
||||||
|
const BreadcrumbSegment = isNotLastCrumb ? BreadcrumbLink : BreadcrumbPage;
|
||||||
|
|
||||||
|
if (el.type === BreadcrumbTypes.Dropdown) {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={`breadcrumb-group-${index + 1}`}>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger>
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbSegment>
|
||||||
|
{el.label} <FontAwesomeIcon icon={faCaretDown} size="sm" />
|
||||||
|
</BreadcrumbSegment>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
{el?.dropdownTitle && <DropdownMenuLabel>{el.dropdownTitle}</DropdownMenuLabel>}
|
||||||
|
{el.links.map((i, dropIndex) => (
|
||||||
|
<Link
|
||||||
|
{...i.link}
|
||||||
|
key={`breadcrumb-group-${index + 1}-dropdown-${dropIndex + 1}`}
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>{i.label}</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
{isNotLastCrumb && <BreadcrumbSeparator />}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.type === BreadcrumbTypes.Component) {
|
||||||
|
const Component = el.component;
|
||||||
|
return (
|
||||||
|
<React.Fragment key={`breadcrumb-group-${index + 1}`}>
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbSegment>
|
||||||
|
<Component />
|
||||||
|
</BreadcrumbSegment>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
{isNotLastCrumb && <BreadcrumbSeparator />}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Icon = el?.icon;
|
||||||
|
return (
|
||||||
|
<React.Fragment key={`breadcrumb-group-${index + 1}`}>
|
||||||
|
{"link" in el && isNotLastCrumb ? (
|
||||||
|
<Link {...el.link}>
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink className="inline-flex items-center gap-1.5">
|
||||||
|
{Icon && <Icon />}
|
||||||
|
{el.label}
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbPage className="inline-flex items-center gap-1.5">
|
||||||
|
{Icon && <Icon />}
|
||||||
|
{el.label}
|
||||||
|
</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
)}
|
||||||
|
{isNotLastCrumb && <BreadcrumbSeparator />}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</BreadcrumbList>
|
||||||
|
</Breadcrumb>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
|
BreadcrumbContainer,
|
||||||
BreadcrumbEllipsis,
|
BreadcrumbEllipsis,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
BreadcrumbLink,
|
BreadcrumbLink,
|
||||||
BreadcrumbList,
|
BreadcrumbList,
|
||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator
|
BreadcrumbSeparator,
|
||||||
|
BreadcrumbTypes
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ComponentPropsWithRef, ElementType, ReactNode, Ref, useRef } from "react";
|
import { ComponentPropsWithRef, ElementType, ReactNode, Ref, useRef } from "react";
|
||||||
import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react";
|
import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react";
|
||||||
import { motion } from "framer-motion";
|
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
export type MenuProps = {
|
export type MenuProps = {
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ export const PageHeader = ({ title, description, children }: Props) => (
|
|||||||
</div>
|
</div>
|
||||||
<div>{children}</div>
|
<div>{children}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="mt-2 text-gray-400">{description}</div>
|
||||||
<p className="mt-2 text-gray-400">{description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,8 +30,9 @@ export const useToggle = (initialState = false): UseToggleReturn => {
|
|||||||
const timedToggle = useCallback((timeout = 2000) => {
|
const timedToggle = useCallback((timeout = 2000) => {
|
||||||
setValue((prev) => !prev);
|
setValue((prev) => !prev);
|
||||||
|
|
||||||
setTimeout(() => {
|
const timeoutRef = setTimeout(() => {
|
||||||
setValue(false);
|
setValue(false);
|
||||||
|
clearTimeout(timeoutRef);
|
||||||
}, timeout);
|
}, timeout);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
import { ReactNode, useState } from "react";
|
import { 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, useRouterState } from "@tanstack/react-router";
|
import { Link, Outlet, useNavigate, useRouter, useRouterState } from "@tanstack/react-router";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { 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 {
|
import {
|
||||||
Breadcrumb,
|
BreadcrumbContainer,
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbLink,
|
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbPage,
|
|
||||||
BreadcrumbSeparator,
|
|
||||||
Menu,
|
Menu,
|
||||||
MenuGroup,
|
MenuGroup,
|
||||||
MenuItem
|
MenuItem,
|
||||||
|
TBreadcrumbFormat
|
||||||
} from "@app/components/v2";
|
} 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";
|
||||||
@@ -30,15 +25,10 @@ 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";
|
||||||
|
|
||||||
type Props = {
|
export const OrganizationLayout = () => {
|
||||||
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>(() => {});
|
||||||
@@ -53,9 +43,6 @@ export const OrganizationLayout = ({ children }: Props) => {
|
|||||||
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 });
|
||||||
@@ -96,168 +83,108 @@ export const OrganizationLayout = ({ children }: Props) => {
|
|||||||
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden transition-all 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
|
<aside className="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">
|
||||||
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} isCollapsed={isCollapsed} />
|
<SidebarHeader onChangeOrg={handleOrgChange} />
|
||||||
<div className="px-1">
|
<div className="px-1">
|
||||||
<AnimatePresence mode="wait">
|
<motion.div
|
||||||
{isCollapsed ? (
|
key="menu-list-items"
|
||||||
<motion.div
|
initial={{ x: 300, opacity: 0 }}
|
||||||
key="menu-icons"
|
animate={{ x: 0, opacity: 1 }}
|
||||||
className="space-y-1"
|
exit={{ x: -300, opacity: 0 }}
|
||||||
animate={{ x: 0, opacity: 1 }}
|
transition={{ duration: 0.1 }}
|
||||||
exit={{ x: -300, opacity: 0 }}
|
>
|
||||||
transition={{ duration: 0.1 }}
|
<Menu>
|
||||||
>
|
<MenuGroup title="PRODUCTS">
|
||||||
<Link to={`/organization/${ProjectType.SecretManager}/overview` as const}>
|
<Link to={`/organization/${ProjectType.SecretManager}/overview` as const}>
|
||||||
<MenuIconButton
|
{({ isActive }) => (
|
||||||
isSelected={window.location.pathname.startsWith(
|
<MenuItem isSelected={isActive} icon="sliding-carousel">
|
||||||
`/${ProjectType.SecretManager}`
|
Secret Management
|
||||||
)}
|
</MenuItem>
|
||||||
icon="sliding-carousel"
|
)}
|
||||||
>
|
|
||||||
Secret Manager
|
|
||||||
</MenuIconButton>
|
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to={`/organization/${ProjectType.CertificateManager}/overview` as const}
|
to={`/organization/${ProjectType.CertificateManager}/overview` as const}
|
||||||
>
|
>
|
||||||
<MenuIconButton
|
{({ isActive }) => (
|
||||||
isSelected={window.location.pathname.startsWith(
|
<MenuItem isSelected={isActive} icon="note">
|
||||||
`/${ProjectType.CertificateManager}`
|
Cert Management
|
||||||
)}
|
</MenuItem>
|
||||||
icon="note"
|
)}
|
||||||
>
|
|
||||||
Cert Manager
|
|
||||||
</MenuIconButton>
|
|
||||||
</Link>
|
</Link>
|
||||||
<Link to={`/organization/${ProjectType.KMS}/overview` as const}>
|
<Link to={`/organization/${ProjectType.KMS}/overview` as const}>
|
||||||
<MenuIconButton
|
{({ isActive }) => (
|
||||||
isSelected={window.location.pathname.startsWith(`/${ProjectType.KMS}`)}
|
<MenuItem isSelected={isActive} icon="unlock">
|
||||||
icon="unlock"
|
Key Management
|
||||||
>
|
</MenuItem>
|
||||||
KMS
|
)}
|
||||||
</MenuIconButton>
|
|
||||||
</Link>
|
</Link>
|
||||||
<Link to={`/organization/${ProjectType.SSH}/overview` as const}>
|
<Link to={`/organization/${ProjectType.SSH}/overview` as const}>
|
||||||
<MenuIconButton
|
{({ isActive }) => (
|
||||||
isSelected={window.location.pathname.startsWith(`/${ProjectType.SSH}`)}
|
<MenuItem isSelected={isActive} icon="verified">
|
||||||
icon="verified"
|
SSH
|
||||||
>
|
</MenuItem>
|
||||||
SSH
|
)}
|
||||||
</MenuIconButton>
|
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
<Link to="/organization/secret-scanning">
|
||||||
) : (
|
{({ isActive }) => (
|
||||||
<motion.div
|
<MenuItem
|
||||||
key="menu-list-items"
|
isSelected={isActive}
|
||||||
initial={{ x: 300, opacity: 0 }}
|
icon="secret-scan"
|
||||||
animate={{ x: 0, opacity: 1 }}
|
className="text-white"
|
||||||
exit={{ x: -300, opacity: 0 }}
|
|
||||||
transition={{ duration: 0.1 }}
|
|
||||||
>
|
|
||||||
<Menu>
|
|
||||||
<MenuGroup title="PRODUCTS">
|
|
||||||
<Link
|
|
||||||
to={`/organization/${ProjectType.SecretManager}/overview` as const}
|
|
||||||
>
|
>
|
||||||
{({ isActive }) => (
|
Secret Scanning
|
||||||
<MenuItem isSelected={isActive} icon="sliding-carousel">
|
</MenuItem>
|
||||||
Secret Management
|
)}
|
||||||
</MenuItem>
|
</Link>
|
||||||
)}
|
</MenuGroup>
|
||||||
</Link>
|
<MenuGroup title="Others">
|
||||||
<Link
|
<Link to="/organization/access-management">
|
||||||
to={
|
{({ isActive }) => (
|
||||||
`/organization/${ProjectType.CertificateManager}/overview` as const
|
<MenuItem isSelected={isActive} icon="groups">
|
||||||
}
|
Access Control
|
||||||
>
|
</MenuItem>
|
||||||
{({ isActive }) => (
|
)}
|
||||||
<MenuItem isSelected={isActive} icon="note">
|
</Link>
|
||||||
Cert Management
|
<Link to="/organization/secret-sharing">
|
||||||
</MenuItem>
|
{({ isActive }) => (
|
||||||
)}
|
<MenuItem isSelected={isActive} icon="lock-closed">
|
||||||
</Link>
|
Secret Sharing
|
||||||
<Link to={`/organization/${ProjectType.KMS}/overview` as const}>
|
</MenuItem>
|
||||||
{({ isActive }) => (
|
)}
|
||||||
<MenuItem isSelected={isActive} icon="unlock">
|
</Link>
|
||||||
Key Management
|
{(window.location.origin.includes("https://app.infisical.com") ||
|
||||||
</MenuItem>
|
window.location.origin.includes("https://eu.infisical.com") ||
|
||||||
)}
|
window.location.origin.includes("https://gamma.infisical.com")) && (
|
||||||
</Link>
|
<Link to="/organization/billing">
|
||||||
<Link to={`/organization/${ProjectType.SSH}/overview` as const}>
|
{({ isActive }) => (
|
||||||
{({ isActive }) => (
|
<MenuItem isSelected={isActive} icon="spinning-coin">
|
||||||
<MenuItem isSelected={isActive} icon="verified">
|
Usage & Billing
|
||||||
SSH
|
</MenuItem>
|
||||||
</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">
|
</Link>
|
||||||
{({ isActive }) => (
|
)}
|
||||||
<MenuItem isSelected={isActive} icon="moving-block">
|
<Link to="/organization/audit-logs">
|
||||||
Audit Logs
|
{({ isActive }) => (
|
||||||
</MenuItem>
|
<MenuItem isSelected={isActive} icon="moving-block">
|
||||||
)}
|
Audit Logs
|
||||||
</Link>
|
</MenuItem>
|
||||||
<Link to="/organization/settings">
|
)}
|
||||||
{({ isActive }) => (
|
</Link>
|
||||||
<MenuItem isSelected={isActive} icon="toggle-settings">
|
<Link to="/organization/settings">
|
||||||
Organization Settings
|
{({ isActive }) => (
|
||||||
</MenuItem>
|
<MenuItem isSelected={isActive} icon="toggle-settings">
|
||||||
)}
|
Organization Settings
|
||||||
</Link>
|
</MenuItem>
|
||||||
</MenuGroup>
|
)}
|
||||||
</Menu>
|
</Link>
|
||||||
</motion.div>
|
</MenuGroup>
|
||||||
)}
|
</Menu>
|
||||||
</AnimatePresence>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SidebarFooter isCollapsed={isCollapsed} onToggleSidebar={setIsCollapsed.toggle} />
|
<SidebarFooter />
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
<CreateOrgModal
|
<CreateOrgModal
|
||||||
@@ -265,34 +192,10 @@ export const OrganizationLayout = ({ children }: Props) => {
|
|||||||
onClose={() => handlePopUpToggle("createOrg", false)}
|
onClose={() => handlePopUpToggle("createOrg", false)}
|
||||||
/>
|
/>
|
||||||
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 px-4 pb-4 dark:[color-scheme:dark]">
|
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 px-4 pb-4 dark:[color-scheme:dark]">
|
||||||
{breadcrumbs && (
|
{breadcrumbs ? (
|
||||||
<div className="mx-auto max-w-7xl py-4 capitalize text-white">
|
<BreadcrumbContainer breadcrumbs={breadcrumbs as TBreadcrumbFormat[]} />
|
||||||
<Breadcrumb>
|
) : null}
|
||||||
<BreadcrumbList>
|
<Outlet />
|
||||||
{breadcrumbs.map((el, index) => {
|
|
||||||
const isNotLastCrumb = index + 1 !== breadcrumbs.length;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{el.link && isNotLastCrumb && !("disabled" in el.link) ? (
|
|
||||||
<Link {...el.link}>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink>{el.label}</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbPage>{el.label}</BreadcrumbPage>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
)}
|
|
||||||
{isNotLastCrumb && <BreadcrumbSeparator />}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{children || <Outlet />}
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+30
-71
@@ -1,13 +1,10 @@
|
|||||||
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
|
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||||
import {
|
import {
|
||||||
faAnglesLeft,
|
|
||||||
faAnglesRight,
|
|
||||||
faArrowUpRightFromSquare,
|
faArrowUpRightFromSquare,
|
||||||
faBook,
|
faBook,
|
||||||
faEnvelope,
|
faEnvelope,
|
||||||
faInfinity,
|
faInfinity,
|
||||||
faInfo,
|
faInfo,
|
||||||
faInfoCircle,
|
|
||||||
faPlus,
|
faPlus,
|
||||||
faQuestion,
|
faQuestion,
|
||||||
faUser
|
faUser
|
||||||
@@ -20,15 +17,12 @@ 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, useUser } from "@app/context";
|
import { useOrganization, useSubscription, useUser } from "@app/context";
|
||||||
import { useGetOrgTrialUrl, useLogoutUser } 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 = [
|
||||||
[
|
[
|
||||||
<FontAwesomeIcon key={1} className="pr-4 text-sm" icon={faSlack} />,
|
<FontAwesomeIcon key={1} className="pr-4 text-sm" icon={faSlack} />,
|
||||||
@@ -52,12 +46,7 @@ export const INFISICAL_SUPPORT_OPTIONS = [
|
|||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
type Props = {
|
export const SidebarFooter = () => {
|
||||||
isCollapsed?: boolean;
|
|
||||||
onToggleSidebar: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SidebarFooter = ({ isCollapsed, onToggleSidebar }: Props) => {
|
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
|
|
||||||
@@ -82,38 +71,28 @@ export const SidebarFooter = ({ isCollapsed, onToggleSidebar }: Props) => {
|
|||||||
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 ${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://app.infisical.com") ||
|
||||||
window.location.origin.includes("https://gamma.infisical.com")) &&
|
window.location.origin.includes("https://gamma.infisical.com")) && <WishForm />}
|
||||||
!isCollapsed && <WishForm />}
|
<Link
|
||||||
{!isCollapsed && (
|
to="/organization/access-management"
|
||||||
<Link
|
search={{
|
||||||
to="/organization/access-management"
|
action: "invite"
|
||||||
search={{
|
}}
|
||||||
action: "invite"
|
className="w-full"
|
||||||
}}
|
>
|
||||||
className="w-full"
|
<div className="mb-3 w-full pl-2 duration-200 hover:text-mineshaft-200">
|
||||||
>
|
<FontAwesomeIcon icon={faPlus} className="mr-3" />
|
||||||
<div className="mb-3 w-full pl-2 duration-200 hover:text-mineshaft-200">
|
Invite people
|
||||||
<FontAwesomeIcon icon={faPlus} className="mr-3" />
|
</div>
|
||||||
Invite people
|
</Link>
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger className="w-full">
|
<DropdownMenuTrigger className="w-full">
|
||||||
{isCollapsed ? (
|
<div className="mb-4 flex w-full items-center pl-2 duration-200 hover:text-mineshaft-200">
|
||||||
<MenuIconButton>
|
<FontAwesomeIcon icon={faQuestion} className="mr-3 px-[0.1rem]" />
|
||||||
<FontAwesomeIcon icon={faInfoCircle} className="mb-3 text-lg" />
|
Help & Support
|
||||||
Support
|
</div>
|
||||||
</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]) => (
|
||||||
@@ -161,39 +140,19 @@ export const SidebarFooter = ({ isCollapsed, onToggleSidebar }: Props) => {
|
|||||||
</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>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger className="w-full">
|
<DropdownMenuTrigger asChild className="w-full">
|
||||||
{isCollapsed ? (
|
<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>
|
<div>
|
||||||
<MenuIconButton>
|
<FontAwesomeIcon icon={faUser} className="text-xs text-mineshaft-400" />
|
||||||
<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>
|
||||||
) : (
|
</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>
|
</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 text-mineshaft-400">{user?.username}</div>
|
||||||
|
|||||||
+16
-164
@@ -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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
@@ -12,14 +12,12 @@ import {
|
|||||||
import { useOrganization } 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, isCollapsed }: Prop) => {
|
export const SidebarHeader = ({ onChangeOrg }: Prop) => {
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data: orgs } = useGetOrganizations();
|
const { data: orgs } = useGetOrganizations();
|
||||||
@@ -36,38 +34,20 @@ export const SidebarHeader = ({ onChangeOrg, isCollapsed }: Prop) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="flex cursor-pointer items-center p-2 pt-4">
|
||||||
className={twMerge(
|
|
||||||
"flex cursor-pointer items-center p-2 pt-4",
|
|
||||||
isCollapsed && "transition-all duration-150 hover:bg-mineshaft-700"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<div
|
<div className="flex w-full items-center justify-center rounded-md border border-mineshaft-600 p-1 transition-all duration-150 hover:bg-mineshaft-700">
|
||||||
className={twMerge(
|
<div className="mr-2 flex h-8 w-8 items-center justify-center rounded-md bg-primary">
|
||||||
"flex w-full items-center justify-center rounded-md border border-mineshaft-600 p-1 transition-all",
|
{currentOrg?.name.charAt(0)}
|
||||||
isCollapsed ? "border-none" : "transition-all duration-150 hover:bg-mineshaft-700"
|
</div>
|
||||||
)}
|
<div className="flex flex-grow flex-col text-white">
|
||||||
>
|
<div className="max-w-36 truncate text-ellipsis text-sm font-medium capitalize">
|
||||||
{isCollapsed ? (
|
{currentOrg?.name}
|
||||||
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-primary">
|
|
||||||
{currentOrg?.name.charAt(0)}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
<div className="text-xs text-mineshaft-400">Free Plan</div>
|
||||||
<>
|
</div>
|
||||||
<div className="mr-2 flex h-8 w-8 items-center justify-center rounded-md bg-primary">
|
<FontAwesomeIcon icon={faSort} className="text-xs text-mineshaft-400" />
|
||||||
{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">
|
||||||
@@ -113,139 +93,11 @@ export const SidebarHeader = ({ onChangeOrg, isCollapsed }: Prop) => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
<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">
|
<DropdownMenuItem onClick={logOutUser} icon={<FontAwesomeIcon icon={faSignOut} />}>
|
||||||
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
Log Out
|
||||||
</button>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</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>
|
|
||||||
|
|||||||
@@ -4,21 +4,18 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { Link, Outlet, useRouterState } from "@tanstack/react-router";
|
import { Link, Outlet, useRouterState } from "@tanstack/react-router";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
BreadcrumbContainer,
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbLink,
|
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbPage,
|
|
||||||
BreadcrumbSeparator,
|
|
||||||
Menu,
|
Menu,
|
||||||
MenuGroup,
|
MenuGroup,
|
||||||
MenuItem
|
MenuItem,
|
||||||
|
TBreadcrumbFormat
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { useGetAccessRequestsCount, useGetSecretApprovalRequestCount } from "@app/hooks/api";
|
import { useGetAccessRequestsCount, useGetSecretApprovalRequestCount } from "@app/hooks/api";
|
||||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
|
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
|
||||||
|
import { MinimizedOrgSidebar } from "./components/MinimizedOrgSidebar";
|
||||||
import { ProjectSelect } from "./components/ProjectSelect";
|
import { ProjectSelect } from "./components/ProjectSelect";
|
||||||
|
|
||||||
// This is a generic layout shared by all types of projects.
|
// This is a generic layout shared by all types of projects.
|
||||||
@@ -54,6 +51,7 @@ export const ProjectLayout = () => {
|
|||||||
<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 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">
|
||||||
|
<MinimizedOrgSidebar />
|
||||||
<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>
|
||||||
@@ -199,34 +197,10 @@ export const ProjectLayout = () => {
|
|||||||
</div>
|
</div>
|
||||||
</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 px-4 pb-4 dark:[color-scheme:dark]">
|
||||||
{breadcrumbs && (
|
{breadcrumbs ? (
|
||||||
<div className="mx-auto max-w-7xl py-4 capitalize text-white">
|
<BreadcrumbContainer breadcrumbs={breadcrumbs as TBreadcrumbFormat[]} />
|
||||||
<Breadcrumb>
|
) : null}
|
||||||
<BreadcrumbList>
|
|
||||||
{breadcrumbs.map((el, index) => {
|
|
||||||
const isNotLastCrumb = index + 1 !== breadcrumbs.length;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{el.link && isNotLastCrumb && !("disabled" in el.link) ? (
|
|
||||||
<Link {...el.link}>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink>{el.label}</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbPage>{el.label}</BreadcrumbPage>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
)}
|
|
||||||
{isNotLastCrumb && <BreadcrumbSeparator />}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
import { ComponentPropsWithRef, ElementType, useRef } from "react";
|
import { ComponentPropsWithRef, ElementType, useRef } from "react";
|
||||||
import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react";
|
import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { MenuItemProps } from "@app/components/v2";
|
import { MenuItemProps } from "@app/components/v2";
|
||||||
import { twMerge } from "tailwind-merge";
|
|
||||||
|
|
||||||
export const MenuIconButton = <T extends ElementType = "button">({
|
export const MenuIconButton = <T extends ElementType = "button">({
|
||||||
children,
|
children,
|
||||||
@@ -10,7 +10,7 @@ export const MenuIconButton = <T extends ElementType = "button">({
|
|||||||
className,
|
className,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
isSelected,
|
isSelected,
|
||||||
as: Item = "button",
|
as: Item = "div",
|
||||||
description,
|
description,
|
||||||
// wrapping in forward ref with generic component causes the loss of ts definitions on props
|
// wrapping in forward ref with generic component causes the loss of ts definitions on props
|
||||||
inputRef,
|
inputRef,
|
||||||
@@ -35,7 +35,7 @@ export const MenuIconButton = <T extends ElementType = "button">({
|
|||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
isSelected ? "visisble" : "invisible"
|
isSelected ? "visisble" : "invisible"
|
||||||
} absolute -left-[0.28rem] h-full w-[0.07rem] rounded-md bg-primary`}
|
} absolute -left-[0.28rem] h-full w-0.5 rounded-md bg-primary`}
|
||||||
/>
|
/>
|
||||||
{icon && (
|
{icon && (
|
||||||
<div className="my-auto mb-2 h-6 w-6">
|
<div className="my-auto mb-2 h-6 w-6">
|
||||||
@@ -49,7 +49,7 @@ export const MenuIconButton = <T extends ElementType = "button">({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<span className="flex-grow break-words text-center text-xxs">{children}</span>
|
<div className="flex-grow justify-center break-words text-center text-xxs">{children}</div>
|
||||||
</Item>
|
</Item>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
+447
@@ -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 (
|
||||||
|
<div className="flex max-h-screen min-h-screen flex-col items-center justify-center gap-2 overflow-y-auto bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700">
|
||||||
|
<Mfa
|
||||||
|
email={user.email as string}
|
||||||
|
method={requiredMfaMethod}
|
||||||
|
successCallback={mfaSuccessCallback}
|
||||||
|
closeMfa={() => toggleShowMfa.off()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<aside className="dark w-16 border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 transition-all duration-150">
|
||||||
|
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
|
||||||
|
<div>
|
||||||
|
<div className="flex cursor-pointer items-center p-2 pt-4 hover:bg-mineshaft-700">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<div className="flex w-full items-center justify-center rounded-md border border-none border-mineshaft-600 p-1 transition-all">
|
||||||
|
<div className="flex h-8 w-8 items-center justify-center rounded-md bg-primary">
|
||||||
|
{currentOrg?.name.charAt(0)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
|
<div className="px-2 py-1 text-xs capitalize text-mineshaft-400">
|
||||||
|
organizations
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOrgChange(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" />
|
||||||
|
<Link to="/organization/secret-manager/overview">
|
||||||
|
<DropdownMenuItem icon={<FontAwesomeIcon icon={faReply} />}>
|
||||||
|
Home
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
<DropdownMenuItem
|
||||||
|
icon={<FontAwesomeIcon icon={faSignOut} />}
|
||||||
|
onClick={logOutUser}
|
||||||
|
>
|
||||||
|
Log Out
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
<div className="px-1">
|
||||||
|
<motion.div
|
||||||
|
key="menu-icons"
|
||||||
|
className="space-y-1"
|
||||||
|
initial={{ x: 300, opacity: 0 }}
|
||||||
|
animate={{ x: 0, opacity: 1 }}
|
||||||
|
exit={{ x: -300, opacity: 0 }}
|
||||||
|
transition={{ duration: 0.1 }}
|
||||||
|
>
|
||||||
|
<DropdownMenu modal>
|
||||||
|
<DropdownMenuTrigger>
|
||||||
|
<MenuIconButton
|
||||||
|
isSelected={window.location.pathname.startsWith(
|
||||||
|
`/${ProjectType.SecretManager}`
|
||||||
|
)}
|
||||||
|
icon="sliding-carousel"
|
||||||
|
>
|
||||||
|
Secret Manager
|
||||||
|
</MenuIconButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
side="right"
|
||||||
|
className="px-3 pb-2"
|
||||||
|
style={{ minWidth: "320px" }}
|
||||||
|
>
|
||||||
|
<ProjectSwitcher type={ProjectType.SecretManager} />
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<DropdownMenu modal>
|
||||||
|
<DropdownMenuTrigger>
|
||||||
|
<MenuIconButton
|
||||||
|
isSelected={window.location.pathname.startsWith(
|
||||||
|
`/${ProjectType.CertificateManager}`
|
||||||
|
)}
|
||||||
|
icon="note"
|
||||||
|
>
|
||||||
|
Cert Manager
|
||||||
|
</MenuIconButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
side="right"
|
||||||
|
className="px-3 pb-2"
|
||||||
|
style={{ minWidth: "320px" }}
|
||||||
|
>
|
||||||
|
<ProjectSwitcher type={ProjectType.CertificateManager} />
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<DropdownMenu modal>
|
||||||
|
<DropdownMenuTrigger className="w-full">
|
||||||
|
<MenuIconButton
|
||||||
|
isSelected={window.location.pathname.startsWith(`/${ProjectType.KMS}`)}
|
||||||
|
icon="unlock"
|
||||||
|
>
|
||||||
|
KMS
|
||||||
|
</MenuIconButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
side="right"
|
||||||
|
className="px-3 pb-2"
|
||||||
|
style={{ minWidth: "320px" }}
|
||||||
|
>
|
||||||
|
<ProjectSwitcher type={ProjectType.KMS} />
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<DropdownMenu modal>
|
||||||
|
<DropdownMenuTrigger className="w-full">
|
||||||
|
<MenuIconButton
|
||||||
|
isSelected={window.location.pathname.startsWith(`/${ProjectType.SSH}`)}
|
||||||
|
icon="verified"
|
||||||
|
>
|
||||||
|
SSH
|
||||||
|
</MenuIconButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
side="right"
|
||||||
|
className="px-3 pb-2"
|
||||||
|
style={{ minWidth: "320px" }}
|
||||||
|
>
|
||||||
|
<ProjectSwitcher type={ProjectType.SSH} />
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<div className="w-full">
|
||||||
|
<MenuIconButton>
|
||||||
|
<div className="flex flex-col items-center justify-center">
|
||||||
|
<FontAwesomeIcon icon={faEllipsis} className="mb-3 text-lg" />
|
||||||
|
<span>More</span>
|
||||||
|
</div>
|
||||||
|
</MenuIconButton>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start" side="right" className="p-1">
|
||||||
|
<DropdownMenuLabel>Organization Options</DropdownMenuLabel>
|
||||||
|
<Link to="/organization/access-management">
|
||||||
|
<DropdownMenuItem icon={<FontAwesomeIcon icon={faUsers} />}>
|
||||||
|
Access Control
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
<Link to="/organization/secret-sharing">
|
||||||
|
<DropdownMenuItem icon={<FontAwesomeIcon icon={faShare} />}>
|
||||||
|
Secret Sharing
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</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">
|
||||||
|
<DropdownMenuItem icon={<FontAwesomeIcon icon={faMoneyBill} />}>
|
||||||
|
Usage & Billing
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
<Link to="/organization/audit-logs">
|
||||||
|
<DropdownMenuItem icon={<FontAwesomeIcon icon={faBook} />}>
|
||||||
|
Audit Logs
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
<Link to="/organization/settings">
|
||||||
|
<DropdownMenuItem icon={<FontAwesomeIcon icon={faCog} />}>
|
||||||
|
Organization Settings
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`relative mt-10 ${
|
||||||
|
subscription && subscription.slug === "starter" && !subscription.has_used_trial
|
||||||
|
? "mb-2"
|
||||||
|
: "mb-4"
|
||||||
|
} flex w-full cursor-default flex-col items-center px-1 text-sm text-mineshaft-400`}
|
||||||
|
>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger className="w-full">
|
||||||
|
<MenuIconButton>
|
||||||
|
<FontAwesomeIcon icon={faInfoCircle} className="mb-3 text-lg" />
|
||||||
|
Support
|
||||||
|
</MenuIconButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
|
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => (
|
||||||
|
<DropdownMenuItem key={url as string}>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href={String(url)}
|
||||||
|
className="flex w-full items-center rounded-md font-normal text-mineshaft-300 duration-200"
|
||||||
|
>
|
||||||
|
<div className="relative flex w-full cursor-pointer select-none items-center justify-start rounded-md">
|
||||||
|
{icon}
|
||||||
|
<div className="text-sm">{text}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
{envConfig.PLATFORM_VERSION && (
|
||||||
|
<div className="mb-2 mt-2 w-full cursor-default pl-5 text-sm duration-200 hover:text-mineshaft-200">
|
||||||
|
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
|
||||||
|
Version: {envConfig.PLATFORM_VERSION}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
{subscription && subscription.slug === "starter" && !subscription.has_used_trial && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={async () => {
|
||||||
|
if (!subscription || !currentOrg) return;
|
||||||
|
|
||||||
|
// direct user to start pro trial
|
||||||
|
const url = await mutateAsync({
|
||||||
|
orgId: currentOrg.id,
|
||||||
|
success_url: window.location.href
|
||||||
|
});
|
||||||
|
|
||||||
|
window.location.href = url;
|
||||||
|
}}
|
||||||
|
className="mt-1.5 w-full"
|
||||||
|
>
|
||||||
|
<div className="justify-left mb-1.5 mt-1.5 flex w-full items-center rounded-md bg-mineshaft-600 py-1 pl-4 text-mineshaft-300 duration-200 hover:bg-mineshaft-500 hover:text-primary-400">
|
||||||
|
<FontAwesomeIcon icon={faInfinity} className="ml-0.5 mr-3 py-2 text-primary" />
|
||||||
|
Start Free Pro Trial
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger className="w-full" asChild>
|
||||||
|
<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>
|
||||||
|
</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" />
|
||||||
|
<DropdownMenuItem onClick={logOutUser} icon={<FontAwesomeIcon icon={faSignOut} />}>
|
||||||
|
Log Out
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
<CreateOrgModal
|
||||||
|
isOpen={popUp?.createOrg?.isOpen}
|
||||||
|
onClose={() => handlePopUpToggle("createOrg", false)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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 (
|
||||||
|
<>
|
||||||
|
<Link to={`/organization/${type}/overview` as const}>
|
||||||
|
<div className="py-2 text-xs capitalize text-bunker-300">
|
||||||
|
{getProjectTitle(type)} projects
|
||||||
|
<FontAwesomeIcon icon={faExternalLink} size="xs" className="ml-1" />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<div className="w-full pb-2">
|
||||||
|
<Input
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faSearch} />}
|
||||||
|
value={search}
|
||||||
|
onChange={(evt) => setSearch(evt.target.value)}
|
||||||
|
size="xs"
|
||||||
|
placeholder="Search by name"
|
||||||
|
className=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="thin-scrollbar max-h-64 overflow-auto">
|
||||||
|
{filteredWorkspaces?.map((el) => (
|
||||||
|
<Link
|
||||||
|
to={`/${type}/$projectId/overview` as const}
|
||||||
|
params={{ projectId: el.id }}
|
||||||
|
key={el.id}
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<span className="capitalize">{el.name}</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
{!isPending && !filteredWorkspaces?.length && (
|
||||||
|
<EmptyState title="No project found" iconSize="1x" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { MinimizedOrgSidebar } from "./MinimizedOrgSidebar";
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
import { Helmet } from "react-helmet";
|
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 { useNavigate, useParams } from "@tanstack/react-router";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
@@ -91,7 +89,7 @@ const Page = () => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="emd" className="p-1">
|
<DropdownMenuContent align="end" className="p-1">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Delete}
|
I={ProjectPermissionActions.Delete}
|
||||||
a={ProjectPermissionSub.CertificateAuthorities}
|
a={ProjectPermissionSub.CertificateAuthorities}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { Helmet } from "react-helmet";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
|
import { PageHeader } from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
|
|
||||||
import { CmekTable } from "./components";
|
import { CmekTable } from "./components";
|
||||||
import { PageHeader } from "@app/components/v2";
|
|
||||||
|
|
||||||
export const OverviewPage = () => {
|
export const OverviewPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@@ -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 { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||||
import { zodValidator } from "@tanstack/zod-adapter";
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -23,7 +25,8 @@ export const Route = createFileRoute(
|
|||||||
context: () => ({
|
context: () => ({
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "home",
|
label: "Home",
|
||||||
|
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||||
link: linkOptions({ to: "/" })
|
link: linkOptions({ to: "/" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { AdminPage } from "./AdminPage";
|
import { AdminPage } from "./AdminPage";
|
||||||
@@ -9,7 +11,8 @@ export const Route = createFileRoute(
|
|||||||
context: () => ({
|
context: () => ({
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "home",
|
label: "Home",
|
||||||
|
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
|
|
||||||
import { LogsSection } from "./components";
|
|
||||||
import { PageHeader } from "@app/components/v2";
|
import { PageHeader } from "@app/components/v2";
|
||||||
|
|
||||||
|
import { LogsSection } from "./components";
|
||||||
|
|
||||||
export const AuditLogsPage = () => {
|
export const AuditLogsPage = () => {
|
||||||
return (
|
return (
|
||||||
<div className="h-full bg-bunker-800">
|
<div className="h-full bg-bunker-800">
|
||||||
|
|||||||
@@ -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 { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { AuditLogsPage } from "./AuditLogsPage";
|
import { AuditLogsPage } from "./AuditLogsPage";
|
||||||
@@ -9,7 +11,8 @@ export const Route = createFileRoute(
|
|||||||
context: () => ({
|
context: () => ({
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "home",
|
label: "Home",
|
||||||
|
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { CertManagerOverviewPage } from "./CertManagerOverviewPage";
|
import { CertManagerOverviewPage } from "./CertManagerOverviewPage";
|
||||||
@@ -9,8 +11,8 @@ export const Route = createFileRoute(
|
|||||||
context: () => ({
|
context: () => ({
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "products",
|
label: "Products",
|
||||||
link: linkOptions({ disabled: true, to: "/" })
|
icon: () => <FontAwesomeIcon icon={faHome} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Cert Management",
|
label: "Cert Management",
|
||||||
|
|||||||
@@ -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 { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage";
|
import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage";
|
||||||
@@ -9,7 +11,8 @@ export const Route = createFileRoute(
|
|||||||
context: () => ({
|
context: () => ({
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "home",
|
label: "Home",
|
||||||
|
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
|
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
|
||||||
@@ -9,7 +11,8 @@ export const Route = createFileRoute(
|
|||||||
context: () => ({
|
context: () => ({
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "home",
|
label: "Home",
|
||||||
|
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { KmsOverviewPage } from "./KmsOverviewPage";
|
import { KmsOverviewPage } from "./KmsOverviewPage";
|
||||||
@@ -9,8 +11,8 @@ export const Route = createFileRoute(
|
|||||||
context: () => ({
|
context: () => ({
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "products",
|
label: "Products",
|
||||||
link: linkOptions({ disabled: true, to: "/" })
|
icon: () => <FontAwesomeIcon icon={faHome} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "KMS",
|
label: "KMS",
|
||||||
|
|||||||
@@ -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 { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { RoleByIDPage } from "./RoleByIDPage";
|
import { RoleByIDPage } from "./RoleByIDPage";
|
||||||
@@ -9,7 +11,8 @@ export const Route = createFileRoute(
|
|||||||
context: () => ({
|
context: () => ({
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "home",
|
label: "Home",
|
||||||
|
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -366,7 +366,7 @@ export const ProductOverviewPage = ({ type }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto flex max-w-7xl flex-col justify-start bg-bunker-800 md:h-screen">
|
<div className="mx-auto flex max-w-7xl flex-col justify-start bg-bunker-800">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
|||||||
@@ -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";
|
import { SecretManagerOverviewPage } from "./SecretManagerOverviewPage";
|
||||||
|
|
||||||
@@ -9,12 +11,11 @@ export const Route = createFileRoute(
|
|||||||
context: () => ({
|
context: () => ({
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "products",
|
label: "Products",
|
||||||
link: linkOptions({ disabled: true, to: "/" })
|
icon: () => <FontAwesomeIcon icon={faHome} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Secret Management",
|
label: "Secret Management"
|
||||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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 { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||||
import { zodValidator } from "@tanstack/zod-adapter";
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -25,11 +27,12 @@ export const Route = createFileRoute(
|
|||||||
context: () => ({
|
context: () => ({
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "home",
|
label: "Home",
|
||||||
|
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||||
link: linkOptions({ to: "/" })
|
link: linkOptions({ to: "/" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "secret scanning"
|
label: "Secret Scanning"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
|
import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { ShareSecretSection } from "./components";
|
|
||||||
import { PageHeader } from "@app/components/v2";
|
import { PageHeader } from "@app/components/v2";
|
||||||
|
|
||||||
|
import { ShareSecretSection } from "./components";
|
||||||
|
|
||||||
export const SecretSharingPage = () => {
|
export const SecretSharingPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|||||||
@@ -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 { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { SecretSharingPage } from "./SecretSharingPage";
|
import { SecretSharingPage } from "./SecretSharingPage";
|
||||||
@@ -9,7 +11,8 @@ export const Route = createFileRoute(
|
|||||||
context: () => ({
|
context: () => ({
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "home",
|
label: "Home",
|
||||||
|
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||||
link: linkOptions({ to: "/" })
|
link: linkOptions({ to: "/" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { OrgTabGroup } from "./components";
|
|
||||||
import { PageHeader } from "@app/components/v2";
|
import { PageHeader } from "@app/components/v2";
|
||||||
|
|
||||||
|
import { OrgTabGroup } from "./components";
|
||||||
|
|
||||||
export const SettingsPage = () => {
|
export const SettingsPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|||||||
@@ -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 { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||||
import { zodValidator } from "@tanstack/zod-adapter";
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -19,7 +21,8 @@ export const Route = createFileRoute(
|
|||||||
context: () => ({
|
context: () => ({
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "home",
|
label: "Home",
|
||||||
|
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||||
link: linkOptions({ to: "/" })
|
link: linkOptions({ to: "/" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { SshOverviewPage } from "./SshOverviewPage";
|
import { SshOverviewPage } from "./SshOverviewPage";
|
||||||
@@ -9,8 +11,8 @@ export const Route = createFileRoute(
|
|||||||
context: () => ({
|
context: () => ({
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "products",
|
label: "Products",
|
||||||
link: linkOptions({ disabled: true, to: "/" })
|
icon: () => <FontAwesomeIcon icon={faHome} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "SSH",
|
label: "SSH",
|
||||||
|
|||||||
@@ -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 { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { UserDetailsByIDPage } from "./UserDetailsByIDPage";
|
import { UserDetailsByIDPage } from "./UserDetailsByIDPage";
|
||||||
@@ -9,7 +11,8 @@ export const Route = createFileRoute(
|
|||||||
context: () => ({
|
context: () => ({
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "home",
|
label: "Home",
|
||||||
|
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useNavigate, useSearch } from "@tanstack/react-router";
|
|||||||
|
|
||||||
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
import { getProjectTitle } from "@app/helpers/project";
|
|
||||||
import { withProjectPermission } from "@app/hoc";
|
import { withProjectPermission } from "@app/hoc";
|
||||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
import { ProjectAccessControlTabs } from "@app/types/project";
|
import { ProjectAccessControlTabs } from "@app/types/project";
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { subject } from "@casl/ability";
|
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 { useNavigate, useParams } from "@tanstack/react-router";
|
||||||
import { format, formatRelative } from "date-fns";
|
import { formatRelative } from "date-fns";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, DeleteActionModal, EmptyState, PageHeader, Spinner } from "@app/components/v2";
|
import { Button, DeleteActionModal, EmptyState, PageHeader, Spinner } from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
import { getProjectTitle } from "@app/helpers/project";
|
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
useDeleteIdentityFromWorkspace,
|
useDeleteIdentityFromWorkspace,
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export const Page = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 p-6 text-white">
|
<div className="container mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 text-white">
|
||||||
{membershipDetails ? (
|
{membershipDetails ? (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ const Page = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container relative mx-auto max-w-7xl pb-12 text-white">
|
<div className="container relative mx-auto max-w-7xl text-white">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{view === IntegrationView.List ? (
|
{view === IntegrationView.List ? (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|||||||
+1
-1
@@ -87,7 +87,7 @@ export const redirectForProviderAuth = (
|
|||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
clientId: integrationOption.clientId,
|
clientId: integrationOption.clientId,
|
||||||
state,
|
state
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|||||||
+2
-2
@@ -80,7 +80,7 @@ export const CloudIntegrationSection = ({
|
|||||||
<NoEnvironmentsBanner projectId={currentWorkspace.id} />
|
<NoEnvironmentsBanner projectId={currentWorkspace.id} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="m-4 mt-7 flex flex-col items-start justify-between px-2 text-xl">
|
<div className="flex flex-col items-start justify-between py-4 text-xl">
|
||||||
{onViewActiveIntegrations && (
|
{onViewActiveIntegrations && (
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
@@ -104,7 +104,7 @@ export const CloudIntegrationSection = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-6 grid grid-cols-3 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7">
|
<div className="mx-2 grid grid-cols-3 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7">
|
||||||
{isLoading &&
|
{isLoading &&
|
||||||
Array.from({ length: 12 }).map((_, index) => (
|
Array.from({ length: 12 }).map((_, index) => (
|
||||||
<Skeleton className="h-32" key={`cloud-integration-skeleton-${index + 1}`} />
|
<Skeleton className="h-32" key={`cloud-integration-skeleton-${index + 1}`} />
|
||||||
|
|||||||
+2
-2
@@ -12,11 +12,11 @@ export const FrameworkIntegrationSection = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mx-4 mb-4 mt-12 flex flex-col items-start justify-between px-2 text-xl">
|
<div className="mb-4 mt-12 flex flex-col items-start justify-between px-2 text-xl">
|
||||||
<h1 className="text-3xl font-semibold">{t("integrations.framework-integrations")}</h1>
|
<h1 className="text-3xl font-semibold">{t("integrations.framework-integrations")}</h1>
|
||||||
<p className="text-base text-gray-400">{t("integrations.click-to-setup")}</p>
|
<p className="text-base text-gray-400">{t("integrations.click-to-setup")}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-6 mt-4 grid grid-cols-3 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7">
|
<div className="mx-2 mt-4 grid grid-cols-3 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-7">
|
||||||
{sortedFrameworks.map((framework) => (
|
{sortedFrameworks.map((framework) => (
|
||||||
<a
|
<a
|
||||||
key={`framework-integration-${framework.slug}`}
|
key={`framework-integration-${framework.slug}`}
|
||||||
|
|||||||
+2
-2
@@ -5,13 +5,13 @@ export const InfrastructureIntegrationSection = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mx-4 mb-4 mt-12 flex flex-col items-start justify-between px-2 text-xl">
|
<div className="mb-4 mt-12 flex flex-col items-start justify-between px-2 text-xl">
|
||||||
<h1 className="text-3xl font-semibold">Infrastructure Integrations</h1>
|
<h1 className="text-3xl font-semibold">Infrastructure Integrations</h1>
|
||||||
<p className="text-base text-gray-400">
|
<p className="text-base text-gray-400">
|
||||||
Click on of the integration to read the documentation.
|
Click on of the integration to read the documentation.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-6 grid grid-cols-2 gap-4 lg:grid-cols-3 2xl:grid-cols-4">
|
<div className="mx-2 grid grid-cols-2 gap-4 lg:grid-cols-3 2xl:grid-cols-4">
|
||||||
{sortedIntegrations.map((integration) => (
|
{sortedIntegrations.map((integration) => (
|
||||||
<a
|
<a
|
||||||
key={`framework-integration-${integration.slug}`}
|
key={`framework-integration-${integration.slug}`}
|
||||||
|
|||||||
@@ -5,5 +5,15 @@ import { IntegrationsListPage } from "./IntegrationsListPage";
|
|||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/"
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/integrations/"
|
||||||
)({
|
)({
|
||||||
component: IntegrationsListPage
|
component: IntegrationsListPage,
|
||||||
|
beforeLoad: ({ context }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Integrations"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { Link, useNavigate, useRouter, useSearch } from "@tanstack/react-router";
|
import { Link, useNavigate, useRouter, useSearch } from "@tanstack/react-router";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import NavHeader from "@app/components/navigation/NavHeader";
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
@@ -34,6 +33,7 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Modal,
|
Modal,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
|
PageHeader,
|
||||||
Pagination,
|
Pagination,
|
||||||
Table,
|
Table,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
@@ -661,22 +661,17 @@ export const OverviewPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div className="">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{t("common.head-title", { title: t("dashboard.title") })}</title>
|
<title>{t("common.head-title", { title: t("dashboard.title") })}</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
|
||||||
<meta property="og:image" content="/images/message.png" />
|
|
||||||
<meta property="og:title" content={String(t("dashboard.og-title"))} />
|
<meta property="og:title" content={String(t("dashboard.og-title"))} />
|
||||||
<meta name="og:description" content={String(t("dashboard.og-description"))} />
|
<meta name="og:description" content={String(t("dashboard.og-description"))} />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="container mx-auto px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
<div className="mx-auto max-w-7xl text-mineshaft-50 dark:[color-scheme:dark]">
|
||||||
<div className="relative right-5 ml-4">
|
<div className="flex w-full items-baseline justify-between">
|
||||||
<NavHeader pageName={t("dashboard.title")} isProjectRelated />
|
<PageHeader
|
||||||
</div>
|
title="Secrets Overview"
|
||||||
<div className="space-y-8">
|
description={
|
||||||
<div className="flex w-full items-baseline justify-between">
|
|
||||||
<div className="mt-6">
|
|
||||||
<p className="text-3xl font-semibold text-bunker-100">Secrets Overview</p>
|
|
||||||
<p className="text-md text-bunker-300">
|
<p className="text-md text-bunker-300">
|
||||||
Inject your secrets using
|
Inject your secrets using
|
||||||
<a
|
<a
|
||||||
@@ -714,32 +709,33 @@ export const OverviewPage = () => {
|
|||||||
>
|
>
|
||||||
more
|
more
|
||||||
</a>
|
</a>
|
||||||
.
|
. Click the Explore button to view the secret details section.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
}
|
||||||
</div>
|
/>
|
||||||
<div className="flex items-center justify-between">
|
</div>
|
||||||
<FolderBreadCrumbs secretPath={secretPath} onResetSearch={handleResetSearch} />
|
<div className="mt-4 flex items-center justify-between">
|
||||||
<div className="flex flex-row items-center justify-center space-x-2">
|
<FolderBreadCrumbs secretPath={secretPath} onResetSearch={handleResetSearch} />
|
||||||
{userAvailableEnvs.length > 0 && (
|
<div className="flex flex-row items-center justify-center space-x-2">
|
||||||
<DropdownMenu>
|
{userAvailableEnvs.length > 0 && (
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenu>
|
||||||
<IconButton
|
<DropdownMenuTrigger asChild>
|
||||||
ariaLabel="Environments"
|
<IconButton
|
||||||
variant="plain"
|
ariaLabel="Environments"
|
||||||
size="sm"
|
variant="plain"
|
||||||
className={twMerge(
|
size="sm"
|
||||||
"flex h-10 w-11 items-center justify-center overflow-hidden border border-mineshaft-600 bg-mineshaft-800 p-0 transition-all hover:border-primary/60 hover:bg-primary/10",
|
className={twMerge(
|
||||||
isTableFiltered && "border-primary/50 text-primary"
|
"flex h-10 w-11 items-center justify-center overflow-hidden border border-mineshaft-600 bg-mineshaft-800 p-0 transition-all hover:border-primary/60 hover:bg-primary/10",
|
||||||
)}
|
isTableFiltered && "border-primary/50 text-primary"
|
||||||
>
|
)}
|
||||||
<Tooltip content="Choose visible environments" className="mb-2">
|
>
|
||||||
<FontAwesomeIcon icon={faList} />
|
<Tooltip content="Choose visible environments" className="mb-2">
|
||||||
</Tooltip>
|
<FontAwesomeIcon icon={faList} />
|
||||||
</IconButton>
|
</Tooltip>
|
||||||
</DropdownMenuTrigger>
|
</IconButton>
|
||||||
<DropdownMenuContent align="end">
|
</DropdownMenuTrigger>
|
||||||
{/* <DropdownMenuItem className="px-1.5" asChild>
|
<DropdownMenuContent align="end">
|
||||||
|
{/* <DropdownMenuItem className="px-1.5" asChild>
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
@@ -751,129 +747,126 @@ export const OverviewPage = () => {
|
|||||||
Create an environment
|
Create an environment
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem> */}
|
</DropdownMenuItem> */}
|
||||||
<DropdownMenuLabel>Filter project resources</DropdownMenuLabel>
|
<DropdownMenuLabel>Filter project resources</DropdownMenuLabel>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleToggleRowType(RowType.Folder);
|
handleToggleRowType(RowType.Folder);
|
||||||
}}
|
}}
|
||||||
icon={filter[RowType.Folder] && <FontAwesomeIcon icon={faCheckCircle} />}
|
icon={filter[RowType.Folder] && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||||
iconPos="right"
|
iconPos="right"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FontAwesomeIcon icon={faFolder} className="text-yellow-700" />
|
<FontAwesomeIcon icon={faFolder} className="text-yellow-700" />
|
||||||
<span>Folders</span>
|
<span>Folders</span>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleToggleRowType(RowType.DynamicSecret);
|
handleToggleRowType(RowType.DynamicSecret);
|
||||||
}}
|
}}
|
||||||
icon={
|
icon={filter[RowType.DynamicSecret] && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||||
filter[RowType.DynamicSecret] && <FontAwesomeIcon icon={faCheckCircle} />
|
iconPos="right"
|
||||||
}
|
>
|
||||||
iconPos="right"
|
<div className="flex items-center gap-2">
|
||||||
>
|
<FontAwesomeIcon icon={faFingerprint} className="text-yellow-700" />
|
||||||
<div className="flex items-center gap-2">
|
<span>Dynamic Secrets</span>
|
||||||
<FontAwesomeIcon icon={faFingerprint} className="text-yellow-700" />
|
</div>
|
||||||
<span>Dynamic Secrets</span>
|
</DropdownMenuItem>
|
||||||
</div>
|
<DropdownMenuItem
|
||||||
</DropdownMenuItem>
|
onClick={(e) => {
|
||||||
<DropdownMenuItem
|
e.preventDefault();
|
||||||
onClick={(e) => {
|
handleToggleRowType(RowType.Secret);
|
||||||
e.preventDefault();
|
}}
|
||||||
handleToggleRowType(RowType.Secret);
|
icon={filter[RowType.Secret] && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||||
}}
|
iconPos="right"
|
||||||
icon={filter[RowType.Secret] && <FontAwesomeIcon icon={faCheckCircle} />}
|
>
|
||||||
iconPos="right"
|
<div className="flex items-center gap-2">
|
||||||
>
|
<FontAwesomeIcon icon={faKey} className="text-bunker-300" />
|
||||||
<div className="flex items-center gap-2">
|
<span>Secrets</span>
|
||||||
<FontAwesomeIcon icon={faKey} className="text-bunker-300" />
|
</div>
|
||||||
<span>Secrets</span>
|
</DropdownMenuItem>
|
||||||
</div>
|
<DropdownMenuLabel>Choose visible environments</DropdownMenuLabel>
|
||||||
</DropdownMenuItem>
|
{userAvailableEnvs.map((availableEnv) => {
|
||||||
<DropdownMenuLabel>Choose visible environments</DropdownMenuLabel>
|
const { id: envId, name } = availableEnv;
|
||||||
{userAvailableEnvs.map((availableEnv) => {
|
|
||||||
const { id: envId, name } = availableEnv;
|
|
||||||
|
|
||||||
const isEnvSelected = visibleEnvs.map((env) => env.id).includes(envId);
|
const isEnvSelected = visibleEnvs.map((env) => env.id).includes(envId);
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleEnvSelect(envId);
|
handleEnvSelect(envId);
|
||||||
}}
|
}}
|
||||||
key={envId}
|
key={envId}
|
||||||
disabled={visibleEnvs?.length === 1}
|
disabled={visibleEnvs?.length === 1}
|
||||||
icon={isEnvSelected && <FontAwesomeIcon icon={faCheckCircle} />}
|
icon={isEnvSelected && <FontAwesomeIcon icon={faCheckCircle} />}
|
||||||
iconPos="right"
|
iconPos="right"
|
||||||
>
|
>
|
||||||
<div className="flex items-center">{name}</div>
|
<div className="flex items-center">{name}</div>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)}
|
||||||
|
<SecretSearchInput
|
||||||
|
value={searchFilter}
|
||||||
|
tags={tags}
|
||||||
|
onChange={setSearchFilter}
|
||||||
|
environments={userAvailableEnvs}
|
||||||
|
projectId={currentWorkspace?.id}
|
||||||
|
/>
|
||||||
|
{userAvailableEnvs.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
variant="outline_bg"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
|
onClick={() => handlePopUpOpen("addSecretsInAllEnvs")}
|
||||||
|
className="h-10 rounded-r-none"
|
||||||
|
>
|
||||||
|
Add Secret
|
||||||
|
</Button>
|
||||||
|
<DropdownMenu
|
||||||
|
open={popUp.misc.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("misc", isOpen)}
|
||||||
|
>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="add-folder-or-import"
|
||||||
|
variant="outline_bg"
|
||||||
|
className="rounded-l-none bg-mineshaft-600 p-3"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faAngleDown} />
|
||||||
|
</IconButton>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<div className="flex flex-col space-y-1 p-1.5">
|
||||||
|
<ProjectPermissionCan
|
||||||
|
I={ProjectPermissionActions.Create}
|
||||||
|
a={ProjectPermissionSub.SecretFolders}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<Button
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faFolderPlus} />}
|
||||||
|
onClick={() => {
|
||||||
|
handlePopUpOpen("addFolder");
|
||||||
|
handlePopUpClose("misc");
|
||||||
|
}}
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
variant="outline_bg"
|
||||||
|
className="h-10"
|
||||||
|
isFullWidth
|
||||||
|
>
|
||||||
|
Add Folder
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
|
</div>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
</div>
|
||||||
<SecretSearchInput
|
)}
|
||||||
value={searchFilter}
|
|
||||||
tags={tags}
|
|
||||||
onChange={setSearchFilter}
|
|
||||||
environments={userAvailableEnvs}
|
|
||||||
projectId={currentWorkspace?.id}
|
|
||||||
/>
|
|
||||||
{userAvailableEnvs.length > 0 && (
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
variant="outline_bg"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
|
||||||
onClick={() => handlePopUpOpen("addSecretsInAllEnvs")}
|
|
||||||
className="h-10 rounded-r-none"
|
|
||||||
>
|
|
||||||
Add Secret
|
|
||||||
</Button>
|
|
||||||
<DropdownMenu
|
|
||||||
open={popUp.misc.isOpen}
|
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("misc", isOpen)}
|
|
||||||
>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="add-folder-or-import"
|
|
||||||
variant="outline_bg"
|
|
||||||
className="rounded-l-none bg-mineshaft-600 p-3"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faAngleDown} />
|
|
||||||
</IconButton>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<div className="flex flex-col space-y-1 p-1.5">
|
|
||||||
<ProjectPermissionCan
|
|
||||||
I={ProjectPermissionActions.Create}
|
|
||||||
a={ProjectPermissionSub.SecretFolders}
|
|
||||||
>
|
|
||||||
{(isAllowed) => (
|
|
||||||
<Button
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faFolderPlus} />}
|
|
||||||
onClick={() => {
|
|
||||||
handlePopUpOpen("addFolder");
|
|
||||||
handlePopUpClose("misc");
|
|
||||||
}}
|
|
||||||
isDisabled={!isAllowed}
|
|
||||||
variant="outline_bg"
|
|
||||||
className="h-10"
|
|
||||||
isFullWidth
|
|
||||||
>
|
|
||||||
Add Folder
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</ProjectPermissionCan>
|
|
||||||
</div>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SelectionPanel
|
<SelectionPanel
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
|
import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
import { Badge } from "@app/components/v2/Badge";
|
import { Badge } from "@app/components/v2/Badge";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { useGetAccessRequestsCount, useGetSecretApprovalRequestCount } from "@app/hooks/api";
|
import { useGetAccessRequestsCount, useGetSecretApprovalRequestCount } from "@app/hooks/api";
|
||||||
@@ -35,39 +35,32 @@ export const SecretApprovalsPage = () => {
|
|||||||
: TabSection.SecretApprovalRequests;
|
: TabSection.SecretApprovalRequests;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{t("common.head-title", { title: t("approval.title") })}</title>
|
<title>{t("common.head-title", { title: t("approval.title") })}</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
|
||||||
<meta property="og:image" content="/images/message.png" />
|
|
||||||
<meta property="og:title" content={String(t("approval.og-title"))} />
|
<meta property="og:title" content={String(t("approval.og-title"))} />
|
||||||
<meta name="og:description" content={String(t("approval.og-description"))} />
|
<meta name="og:description" content={String(t("approval.og-description"))} />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="container mx-auto h-full w-full max-w-7xl bg-bunker-800 px-6 text-white">
|
<div className="container mx-auto h-full w-full max-w-7xl bg-bunker-800 text-white">
|
||||||
<div className="flex items-center justify-between py-6">
|
<PageHeader
|
||||||
<div className="flex w-full flex-col">
|
title="Approval Workflows"
|
||||||
<h2 className="text-3xl font-semibold text-gray-200">Approval Workflows</h2>
|
description="Create approval policies for any modifications to secrets in sensitive environments and folders.
|
||||||
<p className="text-bunker-300">
|
"
|
||||||
Create approval policies for any modifications to secrets in sensitive environments
|
>
|
||||||
and folders.
|
<a
|
||||||
</p>
|
href="https://infisical.com/docs/documentation/platform/pr-workflows"
|
||||||
</div>
|
target="_blank"
|
||||||
<div className="flex w-max justify-center">
|
rel="noopener noreferrer"
|
||||||
<a
|
>
|
||||||
href="https://infisical.com/docs/documentation/platform/pr-workflows"
|
<span className="flex w-max cursor-pointer items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
|
||||||
target="_blank"
|
Documentation
|
||||||
rel="noopener noreferrer"
|
<FontAwesomeIcon
|
||||||
>
|
icon={faArrowUpRightFromSquare}
|
||||||
<span className="flex w-max cursor-pointer items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
|
className="mb-[0.06rem] ml-1 text-xs"
|
||||||
Documentation
|
/>
|
||||||
<FontAwesomeIcon
|
</span>
|
||||||
icon={faArrowUpRightFromSquare}
|
</a>
|
||||||
className="mb-[0.06rem] ml-1 text-xs"
|
</PageHeader>
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Tabs defaultValue={defaultTab}>
|
<Tabs defaultValue={defaultTab}>
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab value={TabSection.SecretApprovalRequests}>
|
<Tab value={TabSection.SecretApprovalRequests}>
|
||||||
|
|||||||
@@ -12,5 +12,15 @@ export const Route = createFileRoute(
|
|||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/approval"
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/approval"
|
||||||
)({
|
)({
|
||||||
component: SecretApprovalsPage,
|
component: SecretApprovalsPage,
|
||||||
validateSearch: zodValidator(SecretApprovalPageQueryParams)
|
validateSearch: zodValidator(SecretApprovalPageQueryParams),
|
||||||
|
beforeLoad: ({ context }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Approvals"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { useNavigate, useParams, useSearch } from "@tanstack/react-router";
|
import { useNavigate, useParams, useSearch } from "@tanstack/react-router";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import NavHeader from "@app/components/navigation/NavHeader";
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { PermissionDeniedBanner } from "@app/components/permissions";
|
import { PermissionDeniedBanner } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
@@ -67,7 +66,6 @@ const LOADER_TEXT = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
const { t } = useTranslation();
|
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const navigate = useNavigate({
|
const navigate = useNavigate({
|
||||||
from: ROUTE_PATHS.SecretManager.SecretDashboardPage.path
|
from: ROUTE_PATHS.SecretManager.SecretDashboardPage.path
|
||||||
@@ -264,18 +262,6 @@ const Page = () => {
|
|||||||
state === OrderByDirection.ASC ? OrderByDirection.DESC : OrderByDirection.ASC
|
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(
|
const handleTagToggle = useCallback(
|
||||||
(tagSlug: string) =>
|
(tagSlug: string) =>
|
||||||
setFilter((state) => {
|
setFilter((state) => {
|
||||||
@@ -396,21 +382,8 @@ const Page = () => {
|
|||||||
setDebouncedSearchFilter("");
|
setDebouncedSearchFilter("");
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto flex flex-col px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
<div className="container mx-auto flex max-w-7xl flex-col text-mineshaft-50 dark:[color-scheme:dark]">
|
||||||
<SecretV2MigrationSection />
|
<SecretV2MigrationSection />
|
||||||
<div className="relative -top-2 right-6 mb-2 ml-6">
|
|
||||||
<NavHeader
|
|
||||||
pageName={t("dashboard.title")}
|
|
||||||
currentEnv={environment}
|
|
||||||
userAvailableEnvs={currentWorkspace?.environments}
|
|
||||||
isFolderMode
|
|
||||||
secretPath={secretPath}
|
|
||||||
isProjectRelated
|
|
||||||
onEnvChange={handleEnvChange}
|
|
||||||
isProtectedBranch={isProtectedBranch}
|
|
||||||
protectionPolicyName={boardPolicy?.name}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{!isRollbackMode ? (
|
{!isRollbackMode ? (
|
||||||
<>
|
<>
|
||||||
<ActionBar
|
<ActionBar
|
||||||
@@ -428,6 +401,7 @@ const Page = () => {
|
|||||||
isSnapshotCountLoading={isSnapshotCountLoading}
|
isSnapshotCountLoading={isSnapshotCountLoading}
|
||||||
onToggleRowType={handleToggleRowType}
|
onToggleRowType={handleToggleRowType}
|
||||||
onClickRollbackMode={() => handlePopUpToggle("snapshots", true)}
|
onClickRollbackMode={() => handlePopUpToggle("snapshots", true)}
|
||||||
|
protectedBranchPolicyName={boardPolicy?.name}
|
||||||
/>
|
/>
|
||||||
<div className="thin-scrollbar mt-3 overflow-y-auto overflow-x-hidden rounded-md rounded-b-none bg-mineshaft-800 text-left text-sm text-bunker-300">
|
<div className="thin-scrollbar mt-3 overflow-y-auto overflow-x-hidden rounded-md rounded-b-none bg-mineshaft-800 text-left text-sm text-bunker-300">
|
||||||
<div className="flex flex-col" id="dashboard">
|
<div className="flex flex-col" id="dashboard">
|
||||||
|
|||||||
+14
-2
@@ -15,6 +15,7 @@ import {
|
|||||||
faFolder,
|
faFolder,
|
||||||
faFolderPlus,
|
faFolderPlus,
|
||||||
faKey,
|
faKey,
|
||||||
|
faLock,
|
||||||
faMinusSquare,
|
faMinusSquare,
|
||||||
faPlus,
|
faPlus,
|
||||||
faTrash
|
faTrash
|
||||||
@@ -80,6 +81,7 @@ type Props = {
|
|||||||
isVisible?: boolean;
|
isVisible?: boolean;
|
||||||
snapshotCount: number;
|
snapshotCount: number;
|
||||||
isSnapshotCountLoading?: boolean;
|
isSnapshotCountLoading?: boolean;
|
||||||
|
protectedBranchPolicyName?: string;
|
||||||
onSearchChange: (term: string) => void;
|
onSearchChange: (term: string) => void;
|
||||||
onToggleTagFilter: (tagId: string) => void;
|
onToggleTagFilter: (tagId: string) => void;
|
||||||
onVisibilityToggle: () => void;
|
onVisibilityToggle: () => void;
|
||||||
@@ -101,7 +103,8 @@ export const ActionBar = ({
|
|||||||
onToggleTagFilter,
|
onToggleTagFilter,
|
||||||
onVisibilityToggle,
|
onVisibilityToggle,
|
||||||
onClickRollbackMode,
|
onClickRollbackMode,
|
||||||
onToggleRowType
|
onToggleRowType,
|
||||||
|
protectedBranchPolicyName
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
|
const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([
|
||||||
"addFolder",
|
"addFolder",
|
||||||
@@ -112,9 +115,9 @@ export const ActionBar = ({
|
|||||||
"misc",
|
"misc",
|
||||||
"upgradePlan"
|
"upgradePlan"
|
||||||
] as const);
|
] as const);
|
||||||
|
const isProtectedBranch = Boolean(protectedBranchPolicyName);
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
const { openPopUp } = usePopUpAction();
|
const { openPopUp } = usePopUpAction();
|
||||||
|
|
||||||
const { mutateAsync: createFolder } = useCreateFolder();
|
const { mutateAsync: createFolder } = useCreateFolder();
|
||||||
const { mutateAsync: deleteBatchSecretV3 } = useDeleteSecretBatch();
|
const { mutateAsync: deleteBatchSecretV3 } = useDeleteSecretBatch();
|
||||||
const { mutateAsync: moveSecrets } = useMoveSecrets();
|
const { mutateAsync: moveSecrets } = useMoveSecrets();
|
||||||
@@ -387,6 +390,15 @@ export const ActionBar = ({
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
{isProtectedBranch && (
|
||||||
|
<Tooltip content={`Protected by policy ${protectedBranchPolicyName}`}>
|
||||||
|
<IconButton variant="outline_bg" ariaLabel="protected">
|
||||||
|
<FontAwesomeIcon icon={faLock} className="text-primary" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="flex-grow" />
|
<div className="flex-grow" />
|
||||||
<div>
|
<div>
|
||||||
<IconButton variant="outline_bg" ariaLabel="Download" onClick={handleSecretDownload}>
|
<IconButton variant="outline_bg" ariaLabel="Download" onClick={handleSecretDownload}>
|
||||||
|
|||||||
@@ -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 { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SecretDashboardPathBreadcrumb } from "@app/components/navigation/SecretDashboardPathBreadcrumb";
|
||||||
|
import { BreadcrumbTypes } from "@app/components/v2";
|
||||||
|
|
||||||
import { SecretDashboardPage } from "./SecretDashboardPage";
|
import { SecretDashboardPage } from "./SecretDashboardPage";
|
||||||
|
|
||||||
const SecretDashboardPageQueryParamsSchema = z.object({
|
const SecretDashboardPageQueryParamsSchema = z.object({
|
||||||
@@ -9,7 +12,6 @@ const SecretDashboardPageQueryParamsSchema = z.object({
|
|||||||
search: z.string().catch(""),
|
search: z.string().catch(""),
|
||||||
tags: z.string().catch("")
|
tags: z.string().catch("")
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug"
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug"
|
||||||
)({
|
)({
|
||||||
@@ -17,5 +19,39 @@ export const Route = createFileRoute(
|
|||||||
validateSearch: zodValidator(SecretDashboardPageQueryParamsSchema),
|
validateSearch: zodValidator(SecretDashboardPageQueryParamsSchema),
|
||||||
search: {
|
search: {
|
||||||
middlewares: [stripSearchParams({ secretPath: "/", search: "", tags: "" })]
|
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: () => (
|
||||||
|
<SecretDashboardPathBreadcrumb
|
||||||
|
secretPathSegments={secretPathSegments}
|
||||||
|
selectedPathSegmentIndex={index}
|
||||||
|
environmentSlug={params.envSlug}
|
||||||
|
projectId={params.projectId}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
DeleteActionModal,
|
DeleteActionModal,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
PageHeader,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Spinner,
|
Spinner,
|
||||||
Table,
|
Table,
|
||||||
@@ -137,30 +138,25 @@ const Page = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto h-full w-full max-w-7xl bg-bunker-800 px-6 text-white">
|
<div className="container mx-auto w-full max-w-7xl bg-bunker-800 text-white">
|
||||||
<div className="flex items-center justify-between py-6">
|
<PageHeader
|
||||||
<div className="flex w-full flex-col">
|
title="Secret Rotation"
|
||||||
<h2 className="text-3xl font-semibold text-gray-200">Secret Rotation</h2>
|
description="Stop manually rotating secrets and automate credential rotation."
|
||||||
<p className="text-bunker-300">
|
>
|
||||||
Stop manually rotating secrets and automate credential rotation.
|
<a
|
||||||
</p>
|
target="_blank"
|
||||||
</div>
|
rel="noopener noreferrer"
|
||||||
<div className="flex w-max justify-center">
|
href="https://infisical.com/docs/documentation/platform/secret-rotation/overview"
|
||||||
<a
|
>
|
||||||
target="_blank"
|
<span className="flex w-max cursor-pointer items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
|
||||||
rel="noopener noreferrer"
|
Documentation
|
||||||
href="https://infisical.com/docs/documentation/platform/secret-rotation/overview"
|
<FontAwesomeIcon
|
||||||
>
|
icon={faArrowUpRightFromSquare}
|
||||||
<span className="flex w-max cursor-pointer items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
|
className="mb-[0.06rem] ml-1 text-xs"
|
||||||
Documentation
|
/>
|
||||||
<FontAwesomeIcon
|
</span>
|
||||||
icon={faArrowUpRightFromSquare}
|
</a>
|
||||||
className="mb-[0.06rem] ml-1 text-xs"
|
</PageHeader>
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<div className="mb-2 mt-6 text-xl font-semibold text-gray-200">Rotated Secrets</div>
|
<div className="mb-2 mt-6 text-xl font-semibold text-gray-200">Rotated Secrets</div>
|
||||||
<div className="flex flex-col space-y-2">
|
<div className="flex flex-col space-y-2">
|
||||||
@@ -374,7 +370,7 @@ export const SecretRotationPage = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full bg-bunker-800">
|
<div className="bg-bunker-800">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
|||||||
@@ -5,5 +5,15 @@ import { SecretRotationPage } from "./SecretRotationPage";
|
|||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secret-rotation"
|
"/_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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+6
-7
@@ -1,13 +1,12 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
|
||||||
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useSearch } from "@tanstack/react-router";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2";
|
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";
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
@@ -18,7 +17,7 @@ type FormData = z.infer<typeof schema>;
|
|||||||
|
|
||||||
export function AzureKeyVaultAuthorizePage() {
|
export function AzureKeyVaultAuthorizePage() {
|
||||||
const { state, clientId } = useSearch({
|
const { state, clientId } = useSearch({
|
||||||
from: ROUTE_PATHS.SecretManager.Integratons.AzureKeyVaultAuthorizePage.id,
|
from: ROUTE_PATHS.SecretManager.Integratons.AzureKeyVaultAuthorizePage.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const { control, handleSubmit } = useForm<FormData>({
|
const { control, handleSubmit } = useForm<FormData>({
|
||||||
@@ -38,7 +37,7 @@ export function AzureKeyVaultAuthorizePage() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>Authorize Azure Key Vault Integration</title>
|
<title>Authorize Azure Key Vault Integration</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<Card className="mb-12 max-w-lg rounded-md border border-mineshaft-600">
|
<Card className="mb-12 max-w-lg rounded-md border border-mineshaft-600">
|
||||||
@@ -56,12 +55,12 @@ export function AzureKeyVaultAuthorizePage() {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<div className="ml-2 mb-1 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
<div className="mb-1 ml-2 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
||||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
||||||
Docs
|
Docs
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faArrowUpRightFromSquare}
|
icon={faArrowUpRightFromSquare}
|
||||||
className="ml-1.5 mb-[0.07rem] text-xxs"
|
className="mb-[0.07rem] ml-1.5 text-xxs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import z from 'zod'
|
import z from "zod";
|
||||||
|
|
||||||
import { AzureKeyVaultAuthorizePage } from "./AzureKeyVaultAuthorizePage";
|
import { AzureKeyVaultAuthorizePage } from "./AzureKeyVaultAuthorizePage";
|
||||||
|
|
||||||
|
|
||||||
const PageQueryParamsSchema = z.object({
|
const PageQueryParamsSchema = z.object({
|
||||||
state: z.string(),
|
state: z.string(),
|
||||||
clientId: z.string().optional(),
|
clientId: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
|
|||||||
+1
-1
@@ -187,7 +187,7 @@ export const CircleCIConfigurePage = () => {
|
|||||||
name="secretPath"
|
name="secretPath"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl label="Secrets Path" errorText={error?.message} isError={Boolean(error)}>
|
<FormControl label="Secrets Path" errorText={error?.message} isError={Boolean(error)}>
|
||||||
<SecretPathInput {...field} environment={selectedEnvironment.slug}/>
|
<SecretPathInput {...field} environment={selectedEnvironment.slug} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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 { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { workspaceKeys } from "@app/hooks/api";
|
import { workspaceKeys } from "@app/hooks/api";
|
||||||
@@ -23,9 +25,11 @@ export const Route = createFileRoute(
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
project,
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
label: "Secret Managers",
|
label: "Secret Managers",
|
||||||
|
icon: () => <FontAwesomeIcon icon={faHome} />,
|
||||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user