Merge pull request #4681 from Infisical/nav-restructure

improvement(frontend): Implement new navigation structure
This commit is contained in:
Scott Wilson
2025-10-20 05:08:44 -07:00
committed by GitHub
125 changed files with 2781 additions and 3045 deletions
@@ -63,14 +63,14 @@ export const WishForm = () => {
open={isOpen} open={isOpen}
> >
<PopoverTrigger asChild> <PopoverTrigger asChild>
<div className="text-md mb-3 w-full pl-5 duration-200 hover:text-mineshaft-200"> <div className="mb-3 w-full cursor-pointer pl-5 text-sm whitespace-nowrap text-mineshaft-400 duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faRocketchat} className="mr-2" /> <FontAwesomeIcon icon={faRocketchat} className="mr-2" />
Request a feature Request a feature
</div> </div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <PopoverContent
hideCloseBtn hideCloseBtn
align="start" align="end"
alignOffset={20} alignOffset={20}
className="mb-1 w-auto border border-mineshaft-600 bg-mineshaft-900 p-4 drop-shadow-2xl" className="mb-1 w-auto border border-mineshaft-600 bg-mineshaft-900 p-4 drop-shadow-2xl"
sticky="always" sticky="always"
+3 -3
View File
@@ -20,11 +20,11 @@ const badgeVariants = cva(
success: "bg-green/20 text-green", success: "bg-green/20 text-green",
org: "bg-org-v1/20 text-org-v1 [&_svg]:text-org-v1 flex items-center opacity-100 hover:bg-org-v1/10 [&_svg]:size-3 gap-x-1 w-min whitespace-nowrap", org: "bg-org-v1/20 text-org-v1 [&_svg]:text-org-v1 flex items-center opacity-100 hover:bg-org-v1/10 [&_svg]:size-3 gap-x-1 w-min whitespace-nowrap",
namespace: namespace:
"bg-namespace-v1/20 text-namespace-v1 [&_svg]:text-namespace-v1 flex opacity-100 hover:bg-namespace-v1/10 items-center [&_svg]:size-3.5 gap-x-1 w-min whitespace-nowrap", "bg-namespace-v1/20 text-namespace-v1 [&_svg]:text-namespace-v1 flex opacity-100 hover:bg-namespace-v1/10 items-center [&_svg]:size-3.5 gap-x-1.5 w-min whitespace-nowrap",
project: project:
"bg-primary/10 text-primary [&_svg]:text-primary opacity-100 hover:bg-primary/10 flex items-center [&_svg]:size-3 gap-x-1 w-min whitespace-nowrap", "bg-primary/10 text-primary [&_svg]:text-primary opacity-100 hover:bg-primary/10 flex items-center [&_svg]:size-3 w-min gap-x-1.5 whitespace-nowrap",
instance: instance:
"bg-mineshaft-200/20 text-mineshaft-200 [&_svg]:text-mineshaft-200 opacity-100 hover:bg-mineshaft-200/20 flex items-center [&_svg]:size-3 gap-x-1 w-min whitespace-nowrap" "bg-mineshaft-200/20 text-mineshaft-200 [&_svg]:text-mineshaft-200 opacity-100 hover:bg-mineshaft-200/20 flex items-center [&_svg]:size-3 gap-x-1.5 w-min whitespace-nowrap"
} }
} }
} }
@@ -120,10 +120,16 @@ export type TBreadcrumbFormat =
icon?: ReactNode; icon?: ReactNode;
}; };
const BreadcrumbContainer = ({ breadcrumbs }: { breadcrumbs: TBreadcrumbFormat[] }) => ( const BreadcrumbContainer = ({
<div className="mx-auto max-w-7xl text-white"> breadcrumbs,
<Breadcrumb> className
<BreadcrumbList> }: {
breadcrumbs: TBreadcrumbFormat[];
className?: string;
}) => (
<div className={twMerge("mx-auto max-w-8xl overflow-hidden text-white", className)}>
<Breadcrumb className="overflow-hidden">
<BreadcrumbList className="overflow-hidden">
{(breadcrumbs as TBreadcrumbFormat[]).map((el, index) => { {(breadcrumbs as TBreadcrumbFormat[]).map((el, index) => {
const isNotLastCrumb = index + 1 !== breadcrumbs.length; const isNotLastCrumb = index + 1 !== breadcrumbs.length;
const BreadcrumbSegment = isNotLastCrumb ? BreadcrumbLink : BreadcrumbPage; const BreadcrumbSegment = isNotLastCrumb ? BreadcrumbLink : BreadcrumbPage;
@@ -165,8 +171,8 @@ const BreadcrumbContainer = ({ breadcrumbs }: { breadcrumbs: TBreadcrumbFormat[]
const Component = el.component; const Component = el.component;
return ( return (
<React.Fragment key={`breadcrumb-group-${index + 1}`}> <React.Fragment key={`breadcrumb-group-${index + 1}`}>
<BreadcrumbItem> <BreadcrumbItem className="overflow-hidden">
<BreadcrumbSegment> <BreadcrumbSegment className="overflow-hidden">
<Component /> <Component />
</BreadcrumbSegment> </BreadcrumbSegment>
</BreadcrumbItem> </BreadcrumbItem>
@@ -5,29 +5,48 @@ import { ReactNode } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { Badge } from "@app/components/v2"; import { Badge } from "@app/components/v2";
import { BadgeProps } from "@app/components/v2/Badge/Badge";
import { ProjectType } from "@app/hooks/api/projects/types";
type Props = { type Props = {
title: ReactNode; title: ReactNode;
description?: ReactNode; description?: ReactNode;
children?: ReactNode; children?: ReactNode;
className?: string; className?: string;
scope: "org" | "project" | "namespace" | "instance"; scope: "org" | "namespace" | "instance" | ProjectType | null;
}; };
const SCOPE_NAME: Record<NonNullable<Props["scope"]>, { label: string; icon: IconDefinition }> = { const SCOPE_NAME: Record<NonNullable<Props["scope"]>, { label: string; icon: IconDefinition }> = {
org: { label: "Organization", icon: faGlobe }, org: { label: "Organization", icon: faGlobe },
project: { label: "Project", icon: faCube }, [ProjectType.SecretManager]: { label: "Project", icon: faCube },
[ProjectType.CertificateManager]: { label: "Project", icon: faCube },
[ProjectType.SSH]: { label: "Project", icon: faCube },
[ProjectType.KMS]: { label: "Project", icon: faCube },
[ProjectType.PAM]: { label: "Project", icon: faCube },
[ProjectType.SecretScanning]: { label: "Project", icon: faCube },
namespace: { label: "Namespace", icon: faCubes }, namespace: { label: "Namespace", icon: faCubes },
instance: { label: "Server", icon: faServer } instance: { label: "Server", icon: faServer }
}; };
const SCOPE_VARIANT: Record<NonNullable<Props["scope"]>, BadgeProps["variant"]> = {
org: "org",
[ProjectType.SecretManager]: "project",
[ProjectType.CertificateManager]: "project",
[ProjectType.SSH]: "project",
[ProjectType.KMS]: "project",
[ProjectType.PAM]: "project",
[ProjectType.SecretScanning]: "project",
namespace: "namespace",
instance: "instance"
};
export const PageHeader = ({ title, description, children, className, scope }: Props) => ( export const PageHeader = ({ title, description, children, className, scope }: Props) => (
<div className={twMerge("mb-4 w-full", className)}> <div className={twMerge("mb-10 w-full", className)}>
<div className="flex w-full justify-between"> <div className="flex w-full justify-between">
<div className="mr-4 flex w-full items-center"> <div className="mr-4 flex w-full items-center">
<h1 className="text-3xl font-medium text-white capitalize">{title}</h1> <h1 className="text-3xl font-medium text-white capitalize">{title}</h1>
{scope && ( {scope && (
<Badge variant={scope} className="mt-1 ml-2.5"> <Badge variant={SCOPE_VARIANT[scope]} className="mt-1 ml-2.5">
<FontAwesomeIcon icon={SCOPE_NAME[scope].icon} /> <FontAwesomeIcon icon={SCOPE_NAME[scope].icon} />
{SCOPE_NAME[scope].label} {SCOPE_NAME[scope].label}
</Badge> </Badge>
+32 -5
View File
@@ -1,10 +1,19 @@
import { IconDefinition } from "@fortawesome/free-brands-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as TabsPrimitive from "@radix-ui/react-tabs"; import * as TabsPrimitive from "@radix-ui/react-tabs";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
export type TabsProps = TabsPrimitive.TabsProps; export type TabsProps = TabsPrimitive.TabsProps;
export const Tabs = ({ className, children, ...props }: TabsProps) => ( export const Tabs = ({ className, children, ...props }: TabsProps) => (
<TabsPrimitive.Root className={twMerge("flex flex-col", className)} {...props}> <TabsPrimitive.Root
className={twMerge(
"flex",
className,
props.orientation === "vertical" ? "flex-col xl:flex-row xl:gap-x-12" : "flex-col"
)}
{...props}
>
{children} {children}
</TabsPrimitive.Root> </TabsPrimitive.Root>
); );
@@ -13,7 +22,11 @@ export type TabListProps = TabsPrimitive.TabsListProps;
export const TabList = ({ className, children, ...props }: TabListProps) => ( export const TabList = ({ className, children, ...props }: TabListProps) => (
<TabsPrimitive.List <TabsPrimitive.List
className={twMerge("flex shrink-0 border-b-2 border-mineshaft-800", className)} className={twMerge(
"no-scrollbar flex shrink-0 overflow-auto border-b-2 border-mineshaft-800",
"data-[orientation=vertical]:xl:flex-col data-[orientation=vertical]:xl:items-start data-[orientation=vertical]:xl:gap-y-6 data-[orientation=vertical]:xl:border-b-0",
className
)}
{...props} {...props}
> >
{children} {children}
@@ -26,18 +39,29 @@ export const Tab = ({
className, className,
children, children,
variant = "project", variant = "project",
icon,
...props ...props
}: TabProps & { variant?: "project" | "namespace" | "org" }) => ( }: TabProps & {
icon?: IconDefinition;
variant?: "project" | "namespace" | "org" | "instance";
}) => (
<TabsPrimitive.Trigger <TabsPrimitive.Trigger
className={twMerge( className={twMerge(
"flex h-10 items-center justify-center px-3 text-sm font-medium text-mineshaft-400 transition-all select-none first:rounded-tl-md last:rounded-tr-md hover:text-mineshaft-200 data-[state=active]:border-b data-[state=active]:text-white", "flex h-10 cursor-pointer items-center justify-center border-transparent",
"px-3 text-sm font-medium whitespace-nowrap text-mineshaft-400 transition-all select-none",
"data-[orientation=vertical]:xl:h-5 data-[orientation=vertical]:xl:border-b-0 data-[orientation=vertical]:xl:border-l",
"border-b hover:text-mineshaft-200",
"data-[state=active]:border-mineshaft-400 data-[state=active]:text-white",
"hover:border-mineshaft-400",
variant === "project" && "data-[state=active]:border-primary", variant === "project" && "data-[state=active]:border-primary",
variant === "namespace" && "data-[state=active]:border-namespace-v1", variant === "namespace" && "data-[state=active]:border-namespace-v1",
variant === "org" && "data-[state=active]:border-org-v1", variant === "org" && "data-[state=active]:border-org-v1",
variant === "instance" && "data-[state=active]:border-mineshaft-300",
className className
)} )}
{...props} {...props}
> >
{icon && <FontAwesomeIcon icon={icon} className="mr-2" size="xs" />}
{children} {children}
</TabsPrimitive.Trigger> </TabsPrimitive.Trigger>
); );
@@ -46,7 +70,10 @@ export type TabPanelProps = TabsPrimitive.TabsContentProps;
export const TabPanel = ({ className, children, ...props }: TabPanelProps) => ( export const TabPanel = ({ className, children, ...props }: TabPanelProps) => (
<TabsPrimitive.Content <TabsPrimitive.Content
className={twMerge("grow rounded-br-md rounded-bl-md py-5 outline-hidden", className)} className={twMerge(
"grow rounded-br-md rounded-bl-md py-5 outline-hidden data-[orientation=vertical]:xl:overflow-x-hidden data-[orientation=vertical]:xl:py-0",
className
)}
{...props} {...props}
> >
{children} {children}
+1 -1
View File
@@ -42,7 +42,7 @@
--font-inter: "Inter", sans-serif; --font-inter: "Inter", sans-serif;
--color-org-v1: #30B3FF; --color-org-v1: #30B3FF;
--color-namespace-v1: #96ff59; --color-namespace-v1: #96ff59;
--max-width-8xl: 88rem; /* 1408px */
/* Primary */ /* Primary */
--color-primary-50: #fffff5; --color-primary-50: #fffff5;
--color-primary-100: #fcfce8; --color-primary-100: #fcfce8;
@@ -12,7 +12,7 @@ import { RedisBanner } from "@app/layouts/OrganizationLayout/components/RedisBan
import { SmtpBanner } from "@app/layouts/OrganizationLayout/components/SmtpBanner"; import { SmtpBanner } from "@app/layouts/OrganizationLayout/components/SmtpBanner";
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner"; import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
import { AdminSidebar } from "./Sidebar"; import { AdminNavBar } from "./AdminNavBar";
export const AdminLayout = () => { export const AdminLayout = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -33,9 +33,9 @@ export const AdminLayout = () => {
{!isLoading && !serverDetails?.emailConfigured && <SmtpBanner />} {!isLoading && !serverDetails?.emailConfigured && <SmtpBanner />}
{!isLoading && subscription.auditLogs && <AuditLogBanner />} {!isLoading && subscription.auditLogs && <AuditLogBanner />}
{!window.isSecureContext && <InsecureConnectionBanner />} {!window.isSecureContext && <InsecureConnectionBanner />}
<div className="flex grow flex-col overflow-y-hidden md:flex-row"> <div className="flex grow flex-col overflow-y-hidden">
<AdminSidebar /> <AdminNavBar />
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-4 pt-8 pb-4 dark:scheme-dark"> <div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4 dark:scheme-dark">
<Outlet /> <Outlet />
</div> </div>
</div> </div>
@@ -0,0 +1,99 @@
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons";
import {
faArrowLeft,
faBuilding,
faCog,
faDatabase,
faKey,
faLock,
faPlug,
faUserTie
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useMatchRoute } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Tab, TabList, Tabs, Tooltip } from "@app/components/v2";
const generalTabs = [
{
label: "General",
icon: faCog,
link: "/admin/"
},
{
label: "Resource Overview",
icon: faBuilding,
link: "/admin/resources/overview"
},
{
label: "Access Control",
icon: faUserTie,
link: "/admin/access-management"
},
{
label: "Encryption",
icon: faLock,
link: "/admin/encryption"
},
{
label: "Authentication",
icon: faCheckCircle,
link: "/admin/authentication"
},
{
label: "Integrations",
icon: faPlug,
link: "/admin/integrations"
},
{
label: "Caching",
icon: faDatabase,
link: "/admin/caching"
},
{
label: "Environment Variables",
icon: faKey,
link: "/admin/environment"
}
];
export const AdminNavBar = () => {
const matchRoute = useMatchRoute();
return (
<div className="border-b border-mineshaft-600 bg-mineshaft-900">
<motion.div
initial={{ x: -150 }}
animate={{ x: 0 }}
exit={{ x: -150 }}
transition={{ duration: 0.2 }}
className="px-4"
>
<nav className="w-full">
<Tabs value="selected">
<TabList className="border-b-0">
<Tooltip position="bottom" content="Back to organization">
<Link to="/organization/projects">
<Tab value="back">
<FontAwesomeIcon icon={faArrowLeft} />
</Tab>
</Link>
</Tooltip>
{generalTabs.map((tab) => {
const isActive = matchRoute({ to: tab.link, fuzzy: false });
return (
<Link key={tab.link} to={tab.link}>
<Tab variant="instance" value={isActive ? "selected" : ""}>
{tab.label}
</Tab>
</Link>
);
})}
</TabList>
</Tabs>
</nav>
</motion.div>
</div>
);
};
@@ -1,126 +0,0 @@
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons";
import {
faBuilding,
faChevronLeft,
faCog,
faDatabase,
faKey,
faLock,
faPlug,
faUserTie
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useMatchRoute } from "@tanstack/react-router";
import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
const generalTabs = [
{
label: "General",
icon: faCog,
link: "/admin/"
},
{
label: "Encryption",
icon: faLock,
link: "/admin/encryption"
},
{
label: "Authentication",
icon: faCheckCircle,
link: "/admin/authentication"
},
{
label: "Integrations",
icon: faPlug,
link: "/admin/integrations"
},
{
label: "Caching",
icon: faDatabase,
link: "/admin/caching"
},
{
label: "Environment Variables",
icon: faKey,
link: "/admin/environment"
}
];
const othersTabs = [
{
label: "Access Controls",
icon: faUserTie,
link: "/admin/access-management"
},
{
label: "Resource Overview",
icon: faBuilding,
link: "/admin/resources/overview"
}
];
export const AdminSidebar = () => {
const matchRoute = useMatchRoute();
return (
<aside className="dark w-full border-r border-mineshaft-600 bg-linear-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:scheme-dark">
<div className="flex-1">
<Menu>
<MenuGroup title="Configuration">
{generalTabs.map((tab) => {
const isActive = matchRoute({ to: tab.link, fuzzy: false });
return (
<Link key={tab.link} to={tab.link}>
<MenuItem isSelected={Boolean(isActive)}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={tab.icon} />
</div>
{tab.label}
</div>
</MenuItem>
</Link>
);
})}
</MenuGroup>
<MenuGroup title="Others">
{othersTabs.map((tab) => {
const isActive = matchRoute({ to: tab.link, fuzzy: false });
return (
<Link key={tab.link} to={tab.link}>
<MenuItem isSelected={Boolean(isActive)}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={tab.icon} />
</div>
{tab.label}
</div>
</MenuItem>
</Link>
);
})}
</MenuGroup>
</Menu>
</div>
<Menu>
<Link to="/organization/projects">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<FontAwesomeIcon
className="mx-1 inline-block shrink-0"
icon={faChevronLeft}
flip="vertical"
/>
}
>
Back to Organization
</MenuItem>
</Link>
</Menu>
</nav>
</aside>
);
};
+65 -124
View File
@@ -1,9 +1,7 @@
import { faBook, faCog, faCube, faHome, faLock, faUsers } from "@fortawesome/free-solid-svg-icons"; import { Link, Outlet, useLocation } from "@tanstack/react-router";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2"; import { Tab, TabList, Tabs } from "@app/components/v2";
import { useProject, useProjectPermission } from "@app/context"; import { useProject, useProjectPermission } from "@app/context";
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner"; import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
@@ -12,138 +10,81 @@ export const KmsLayout = () => {
const { currentProject } = useProject(); const { currentProject } = useProject();
const { assumedPrivilegeDetails } = useProjectPermission(); const { assumedPrivilegeDetails } = useProjectPermission();
const location = useLocation();
return ( return (
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex"> <div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="flex grow flex-col overflow-y-hidden md:flex-row"> <div className="border-b border-mineshaft-600 bg-mineshaft-900">
<motion.div <motion.div
key="menu-project-items" key="menu-project-items"
initial={{ x: -150 }} initial={{ x: -150 }}
animate={{ x: 0 }} animate={{ x: 0 }}
exit={{ x: -150 }} exit={{ x: -150 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="dark w-full border-r border-mineshaft-600 bg-linear-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60" className="px-4"
> >
<nav className="items-between flex h-full flex-col overflow-y-auto dark:scheme-dark"> <nav className="w-full">
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white"> <Tabs value="selected">
<Lottie className="inline-block h-5 w-5 shrink-0" icon="unlock" /> <TabList className="border-b-0">
KMS <Link
</div> to="/projects/kms/$projectId/overview"
<div className="flex-1"> params={{
<Menu> projectId: currentProject.id
<MenuGroup title="Resources"> }}
<Link >
to="/projects/kms/$projectId/overview" {({ isActive }) => <Tab value={isActive ? "selected" : ""}>Overview</Tab>}
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCube} />
</div>
Overview
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/kms/$projectId/kmip"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faLock} />
</div>
KMIP
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Others">
<Link
to="/projects/kms/$projectId/access-management"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faUsers} />
</div>
Project Access
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/kms/$projectId/audit-logs"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBook} />
</div>
Audit Logs
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/kms/$projectId/settings"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCog} />
</div>
Project Settings
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
</div>
<div>
<Menu>
<Link to="/organization/projects">
<MenuItem
variant="project"
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
</div>
}
>
Organization Home
</MenuItem>
</Link> </Link>
</Menu> <Link
</div> to="/projects/kms/$projectId/kmip"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>KMIP</Tab>}
</Link>
<Link
to="/projects/kms/$projectId/access-management"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab
value={
isActive ||
location.pathname.match(/\/groups\/|\/identities\/|\/members\/|\/roles\//)
? "selected"
: ""
}
>
Access Control
</Tab>
)}
</Link>
<Link
to="/projects/kms/$projectId/audit-logs"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Audit Logs</Tab>}
</Link>
<Link
to="/projects/kms/$projectId/settings"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Settings</Tab>}
</Link>
</TabList>
</Tabs>
</nav> </nav>
</motion.div> </motion.div>
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 p-4 pt-8"> </div>
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />} {assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
<Outlet /> <div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4">
</div> <Outlet />
</div> </div>
</div> </div>
); );
@@ -13,7 +13,7 @@ import { useFetchServerStatus } from "@app/hooks/api";
import { AuditLogBanner } from "./components/AuditLogBanner"; import { AuditLogBanner } from "./components/AuditLogBanner";
import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner"; import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner";
import { Navbar } from "./components/NavBar"; import { Navbar } from "./components/NavBar";
import { OrgSidebar } from "./components/OrgSidebar"; import { OrgNavBar } from "./components/OrgNavBar";
import { RedisBanner } from "./components/RedisBanner"; import { RedisBanner } from "./components/RedisBanner";
import { SmtpBanner } from "./components/SmtpBanner"; import { SmtpBanner } from "./components/SmtpBanner";
@@ -41,16 +41,16 @@ export const OrganizationLayout = () => {
className={`dark hidden ${containerHeight} w-full flex-col overflow-x-hidden bg-bunker-800 transition-all md:flex`} className={`dark hidden ${containerHeight} w-full flex-col overflow-x-hidden bg-bunker-800 transition-all md:flex`}
> >
<Navbar /> <Navbar />
{!isLoading && !serverDetails?.redisConfigured && <RedisBanner />} <div className="flex grow flex-col overflow-y-hidden">
{!isLoading && !serverDetails?.emailConfigured && <SmtpBanner />} <OrgNavBar isHidden={isInsideProject} />
{!isLoading && subscription.auditLogs && <AuditLogBanner />} {!isLoading && !isInsideProject && !serverDetails?.redisConfigured && <RedisBanner />}
{!window.isSecureContext && <InsecureConnectionBanner />} {!isLoading && !isInsideProject && !serverDetails?.emailConfigured && <SmtpBanner />}
<div className="flex grow flex-col overflow-y-hidden md:flex-row"> {!isLoading && !isInsideProject && subscription.auditLogs && <AuditLogBanner />}
<OrgSidebar isHidden={isInsideProject} /> {!window.isSecureContext && !isInsideProject && <InsecureConnectionBanner />}
<main <main
className={twMerge( className={twMerge(
"flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-4 pt-8 pb-4 dark:scheme-dark", "flex-1 overflow-x-hidden bg-bunker-800 px-12 pt-10 pb-4 dark:scheme-dark",
isInsideProject && "p-0" isInsideProject ? "overflow-y-hidden p-0" : "overflow-y-auto"
)} )}
> >
<Outlet /> <Outlet />
@@ -9,6 +9,7 @@ import {
faEnvelope, faEnvelope,
faExclamationTriangle, faExclamationTriangle,
faGlobe, faGlobe,
faInfinity,
faInfo, faInfo,
faInfoCircle, faInfoCircle,
faServer, faServer,
@@ -43,7 +44,7 @@ import { envConfig } from "@app/config/env";
import { useOrganization, useSubscription, useUser } from "@app/context"; import { useOrganization, useSubscription, useUser } from "@app/context";
import { isInfisicalCloud } from "@app/helpers/platform"; import { isInfisicalCloud } from "@app/helpers/platform";
import { useToggle } from "@app/hooks"; import { useToggle } from "@app/hooks";
import { projectKeys, useGetOrganizations, useLogoutUser } from "@app/hooks/api"; import { projectKeys, useGetOrganizations, useGetOrgTrialUrl, useLogoutUser } from "@app/hooks/api";
import { authKeys, selectOrganization } from "@app/hooks/api/auth/queries"; import { authKeys, selectOrganization } from "@app/hooks/api/auth/queries";
import { MfaMethod } from "@app/hooks/api/auth/types"; import { MfaMethod } from "@app/hooks/api/auth/types";
import { getAuthToken } from "@app/hooks/api/reactQuery"; import { getAuthToken } from "@app/hooks/api/reactQuery";
@@ -162,6 +163,8 @@ export const Navbar = () => {
await navigateUserToOrg(navigate, orgId); await navigateUserToOrg(navigate, orgId);
}; };
const { mutateAsync } = useGetOrgTrialUrl();
const logout = useLogoutUser(); const logout = useLogoutUser();
const logOutUser = async () => { const logOutUser = async () => {
try { try {
@@ -201,144 +204,192 @@ export const Navbar = () => {
const isServerAdminPanel = location.pathname.startsWith("/admin"); const isServerAdminPanel = location.pathname.startsWith("/admin");
const isOrgScope = breadcrumbs?.length === 1; // TODO: scott/akhil is this adequate? const isOrgScope = location.pathname.startsWith("/organization"); // TODO: scott/akhil is this adequate?
return ( return (
<div className="z-10 flex min-h-12 items-center border-b border-mineshaft-600 bg-mineshaft-800 px-4"> <div className="z-10 flex min-h-12 items-center bg-mineshaft-900 px-4 pt-1">
<div> <div className="mr-auto flex items-center overflow-hidden">
<Link to="/organization/projects"> <div className="shrink-0">
<img alt="infisical logo" src="/images/logotransparent.png" className="h-4" /> <Link to="/organization/projects">
</Link> <img alt="infisical logo" src="/images/logotransparent.png" className="h-4" />
</div>
<p className="pr-3 pl-1 text-lg text-mineshaft-400/70">/</p>
{isServerAdminPanel ? (
<>
<Link
to="/admin"
className="group flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary"
>
<div>
<FontAwesomeIcon icon={faServer} className="text-xs text-bunker-300" />
</div>
<div className="whitespace-nowrap">Server Console</div>
</Link> </Link>
<p className="pr-3 pl-3 text-lg text-mineshaft-400/70">/</p> </div>
{breadcrumbs ? ( <p className="pr-3 pl-1 text-lg text-mineshaft-400/70">/</p>
// scott: remove /admin as we show server console above {isServerAdminPanel ? (
<BreadcrumbContainer breadcrumbs={breadcrumbs.slice(1) as TBreadcrumbFormat[]} /> <>
) : null} <Link
</> to="/admin"
) : ( className="group flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary"
<> >
<div className="flex items-center"> <div>
<DropdownMenu modal={false}> <FontAwesomeIcon icon={faServer} className="text-xs text-bunker-300" />
<Link to="/organization/projects"> </div>
<div className="group flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary"> <div className="whitespace-nowrap">Server Console</div>
<Badge </Link>
variant="org" <p className="pr-3 pl-3 text-lg text-mineshaft-400/70">/</p>
className={twMerge("text-sm", !isOrgScope && "bg-transparent opacity-75")} {breadcrumbs ? (
> // scott: remove /admin as we show server console above
<FontAwesomeIcon icon={faGlobe} /> <BreadcrumbContainer breadcrumbs={breadcrumbs.slice(1) as TBreadcrumbFormat[]} />
{currentOrg?.name} ) : null}
</Badge> </>
<div className="mr-1 rounded-sm border border-mineshaft-500 px-1 text-xs text-bunker-300 no-underline!"> ) : (
{getPlan(subscription)} <>
</div> <div className="flex items-center overflow-hidden">
{subscription.cardDeclined && ( <DropdownMenu modal={false}>
<Tooltip <Link className="overflow-hidden" to="/organization/projects">
content={`Your payment could not be processed${subscription.cardDeclinedReason ? `: ${subscription.cardDeclinedReason}` : ""}. Please update your payment method to continue enjoying premium features.`} <div className="group flex cursor-pointer items-center gap-2 overflow-hidden text-sm text-white transition-all duration-100 hover:text-primary">
className="max-w-xs" <Badge
variant="org"
className={twMerge(
"max-w-full min-w-0 cursor-pointer text-sm",
!isOrgScope &&
"bg-transparent text-mineshaft-200 hover:bg-transparent hover:underline"
)}
> >
<div className="flex items-center"> <FontAwesomeIcon icon={faGlobe} />
<FontAwesomeIcon <p className="truncate">{currentOrg?.name}</p>
icon={faExclamationTriangle} </Badge>
className="animate-pulse cursor-help text-xs text-primary-400" <div className="mr-1 rounded-sm border border-mineshaft-500 px-1 text-xs text-bunker-300 no-underline!">
/> {getPlan(subscription)}
</div> </div>
</Tooltip> {subscription.cardDeclined && (
)} <Tooltip
</div> content={`Your payment could not be processed${subscription.cardDeclinedReason ? `: ${subscription.cardDeclinedReason}` : ""}. Please update your payment method to continue enjoying premium features.`}
</Link> className="max-w-xs"
<DropdownMenuTrigger asChild>
<div>
<IconButton
variant="plain"
colorSchema="secondary"
ariaLabel="switch-org"
className="px-2 py-1"
>
<FontAwesomeIcon icon={faCaretDown} className="text-xs text-bunker-300" />
</IconButton>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
side="bottom"
className="mt-6 cursor-default p-1 shadow-mineshaft-600 drop-shadow-md"
style={{ minWidth: "220px" }}
>
<div className="px-2 py-1 text-xs text-mineshaft-400 capitalize">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;
}
if (org.googleSsoAuthEnforced) {
await logout.mutateAsync();
window.open(`/api/v1/sso/redirect/google?org_slug=${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"> <div className="flex items-center">
{org.name} <FontAwesomeIcon
icon={faExclamationTriangle}
className="animate-pulse cursor-help text-xs text-primary-400"
/>
</div> </div>
</Button> </Tooltip>
</DropdownMenuItem> )}
); </div>
})} </Link>
<div className="mt-1 h-1 border-t border-mineshaft-600" /> <DropdownMenuTrigger asChild>
<DropdownMenuItem icon={<FontAwesomeIcon icon={faSignOut} />} onClick={logOutUser}> <div>
Log Out <IconButton
</DropdownMenuItem> variant="plain"
</DropdownMenuContent> colorSchema="secondary"
</DropdownMenu> ariaLabel="switch-org"
</div> className="px-2 py-1"
<p className="pr-3 pl-1 text-lg text-mineshaft-400/70">/</p> >
{breadcrumbs ? ( <FontAwesomeIcon icon={faCaretDown} className="text-xs text-bunker-300" />
<BreadcrumbContainer breadcrumbs={breadcrumbs as TBreadcrumbFormat[]} /> </IconButton>
) : null} </div>
</> </DropdownMenuTrigger>
<DropdownMenuContent
align="start"
side="bottom"
className="mt-6 cursor-default p-1 shadow-mineshaft-600 drop-shadow-md"
style={{ minWidth: "220px" }}
>
<div className="px-2 py-1 text-xs text-mineshaft-400 capitalize">
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;
}
if (org.googleSsoAuthEnforced) {
await logout.mutateAsync();
window.open(`/api/v1/sso/redirect/google?org_slug=${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" />
<DropdownMenuItem
icon={<FontAwesomeIcon icon={faSignOut} />}
onClick={logOutUser}
>
Log Out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
{!isOrgScope && (
<>
<p className="pr-3 pl-1 text-lg text-mineshaft-400/70">/</p>
{breadcrumbs ? (
<BreadcrumbContainer
className="min-w-[15rem] flex-1"
breadcrumbs={[breadcrumbs[0]] as TBreadcrumbFormat[]}
/>
) : null}
</>
)}
</>
)}
</div>
{subscription && subscription.slug === "starter" && !subscription.has_used_trial && (
<Tooltip content="Start Free Pro Trial">
<Button
variant="plain"
className="mr-2 border-mineshaft-500 px-2.5 py-1.5 whitespace-nowrap text-mineshaft-200 hover:bg-mineshaft-600"
leftIcon={<FontAwesomeIcon icon={faInfinity} />}
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;
}}
>
Free Pro Trial
</Button>
</Tooltip>
)}
{user.superAdmin && !location.pathname.startsWith("/admin") && (
<Link
className="mr-2 rounded-md border border-mineshaft-500 px-2.5 py-1.5 text-sm whitespace-nowrap text-mineshaft-200 hover:bg-mineshaft-600"
to="/admin"
>
<FontAwesomeIcon icon={faServer} className="mr-2" />
Server Console
</Link>
)} )}
<div className="grow" />
<DropdownMenu modal={false}> <DropdownMenu modal={false}>
<DropdownMenuTrigger> <DropdownMenuTrigger>
<div className="rounded-l-md border border-r-0 border-mineshaft-500 px-2.5 py-1 hover:bg-mineshaft-600"> <div className="rounded-l-md border border-r-0 border-mineshaft-500 px-2.5 py-1 hover:bg-mineshaft-600">
@@ -0,0 +1,109 @@
import { Link, useLocation } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
import { Tab, TabList, Tabs } from "@app/components/v2";
import { usePopUp } from "@app/hooks";
type Props = {
isHidden?: boolean;
};
export const OrgNavBar = ({ isHidden }: Props) => {
const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const);
const { pathname } = useLocation();
return (
<>
{!isHidden && (
<div className="dark hidden w-full flex-col overflow-x-hidden border-b border-mineshaft-600 bg-mineshaft-900 px-4 md:flex">
<motion.div
key="menu-org-items"
initial={{ x: -150 }}
animate={{ x: 0 }}
exit={{ x: -150 }}
transition={{ duration: 0.2 }}
>
<nav className="w-full">
<Tabs value="selected">
<TabList className="border-b-0">
<Link to="/organization/projects">
{({ isActive }) => (
<Tab variant="org" value={isActive ? "selected" : ""}>
Overview
</Tab>
)}
</Link>
<Link to="/organization/app-connections">
{({ isActive }) => (
<Tab variant="org" value={isActive ? "selected" : ""}>
App Connections
</Tab>
)}
</Link>
<Link to="/organization/networking">
{({ isActive }) => (
<Tab variant="org" value={isActive ? "selected" : ""}>
Networking
</Tab>
)}
</Link>
<Link to="/organization/secret-sharing">
{({ isActive }) => (
<Tab value={isActive ? "selected" : ""} variant="org">
Secret Sharing
</Tab>
)}
</Link>
<Link to="/organization/access-management">
{({ isActive }) => (
<Tab
variant="org"
value={
isActive ||
pathname.match(
/organization\/members|organization\/identities|organization\/groups|organization\/roles/
)
? "selected"
: ""
}
>
Access Control
</Tab>
)}
</Link>
<Link to="/organization/audit-logs">
{({ isActive }) => (
<Tab variant="org" value={isActive ? "selected" : ""}>
Audit Logs
</Tab>
)}
</Link>
<Link to="/organization/billing">
{({ isActive }) => (
<Tab variant="org" value={isActive ? "selected" : ""}>
Usage & Billing
</Tab>
)}
</Link>
<Link to="/organization/settings">
{({ isActive }) => (
<Tab variant="org" value={isActive ? "selected" : ""}>
Settings
</Tab>
)}
</Link>
</TabList>
</Tabs>
</nav>
</motion.div>
</div>
)}
<CreateOrgModal
isOpen={popUp?.createOrg?.isOpen}
onClose={() => handlePopUpToggle("createOrg", false)}
/>
</>
);
};
@@ -0,0 +1 @@
export { OrgNavBar } from "./OrgNavBar";
@@ -1,214 +0,0 @@
import {
faBook,
faCog,
faInfinity,
faMoneyBill,
faNetworkWired,
faPlug,
faShare,
faTable,
faUsers,
faUserTie
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link } from "@tanstack/react-router";
import { AnimatePresence, motion } from "framer-motion";
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
import { Menu, MenuGroup, MenuItem, Tooltip } from "@app/components/v2";
import { useOrganization, useSubscription, useUser } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useGetOrgTrialUrl } from "@app/hooks/api";
type Props = {
isHidden?: boolean;
};
export const OrgSidebar = ({ isHidden }: Props) => {
const { subscription } = useSubscription();
const { user } = useUser();
const { mutateAsync } = useGetOrgTrialUrl();
const { currentOrg } = useOrganization();
const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const);
return (
<>
<AnimatePresence mode="popLayout">
{!isHidden && (
<motion.aside
key="org-sidebar"
transition={{ duration: 0.3 }}
initial={{ opacity: 0, translateX: -240 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -240 }}
layout
className="dark z-10 w-60 border-r border-mineshaft-600 bg-linear-to-tr from-mineshaft-800 to-mineshaft-900"
>
<nav className="items-between flex h-full flex-col overflow-y-auto dark:scheme-dark">
<Menu>
<MenuGroup title="Overview">
<Link to="/organization/projects">
{({ isActive }) => (
<MenuItem variant="org" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faTable} />
</div>
Overview
</div>
</MenuItem>
)}
</Link>
<Link to="/organization/access-management">
{({ isActive }) => (
<MenuItem variant="org" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faUsers} />
</div>
Organization Access
</div>
</MenuItem>
)}
</Link>
<Link to="/organization/billing">
{({ isActive }) => (
<MenuItem variant="org" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faMoneyBill} className="mr-4" />
</div>
Usage & Billing
</div>
</MenuItem>
)}
</Link>
<Link to="/organization/audit-logs">
{({ isActive }) => (
<MenuItem variant="org" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBook} className="mr-4" />
</div>
Audit Logs
</div>
</MenuItem>
)}
</Link>
<Link to="/organization/settings">
{({ isActive }) => (
<MenuItem variant="org" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCog} className="mr-4" />
</div>
Organization Settings
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Resources">
<Link to="/organization/app-connections">
{({ isActive }) => (
<MenuItem variant="org" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faPlug} className="mr-4" />
</div>
App Connections
</div>
</MenuItem>
)}
</Link>
<Link to="/organization/networking">
{({ isActive }) => (
<MenuItem variant="org" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faNetworkWired} className="mr-4" />
</div>
Networking
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
<div className="grow" />
<Menu>
{subscription &&
subscription.slug === "starter" &&
!subscription.has_used_trial && (
<Tooltip content="Start Free Pro Trial">
<MenuItem
variant="org"
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<FontAwesomeIcon
className="mx-1 inline-block shrink-0"
icon={faInfinity}
/>
}
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;
}}
>
Pro Trial
</MenuItem>
</Tooltip>
)}
<Link to="/organization/secret-sharing">
<MenuItem
variant="org"
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faShare} />
</div>
}
>
Share Secret
</MenuItem>
</Link>
{user.superAdmin && (
<Link to="/admin">
<MenuItem
variant="org"
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon
className="mx-1 inline-block shrink-0"
icon={faUserTie}
/>
</div>
}
>
Server Console
</MenuItem>
</Link>
)}
</Menu>
</nav>
</motion.aside>
)}
</AnimatePresence>
<CreateOrgModal
isOpen={popUp?.createOrg?.isOpen}
onClose={() => handlePopUpToggle("createOrg", false)}
/>
</>
);
};
@@ -1 +0,0 @@
export { OrgSidebar } from "./OrgSidebar";
+72 -149
View File
@@ -1,19 +1,9 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { import { Link, Outlet, useLocation } from "@tanstack/react-router";
faBook,
faBoxOpen,
faCog,
faDisplay,
faHome,
faUser,
faUsers
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2"; import { Tab, TabList, Tabs } from "@app/components/v2";
import { useProject, useProjectPermission, useSubscription } from "@app/context"; import { useProject, useProjectPermission, useSubscription } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
@@ -23,7 +13,7 @@ export const PamLayout = () => {
const { currentProject } = useProject(); const { currentProject } = useProject();
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const { assumedPrivilegeDetails } = useProjectPermission(); const { assumedPrivilegeDetails } = useProjectPermission();
const location = useLocation();
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"]); const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"]);
useEffect(() => { useEffect(() => {
@@ -35,152 +25,85 @@ export const PamLayout = () => {
return ( return (
<> <>
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex"> <div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="flex grow flex-col overflow-y-hidden md:flex-row"> <div className="border-b border-mineshaft-600 bg-mineshaft-900">
<motion.div <motion.div
key="menu-project-items" key="menu-project-items"
initial={{ x: -150 }} initial={{ x: -150 }}
animate={{ x: 0 }} animate={{ x: 0 }}
exit={{ x: -150 }} exit={{ x: -150 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="dark w-full border-r border-mineshaft-600 bg-linear-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60" className="px-4"
> >
<nav className="items-between flex h-full flex-col overflow-y-auto dark:scheme-dark"> <nav className="w-full">
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white"> <Tabs value="selected">
<Lottie className="inline-block h-5 w-5 shrink-0" icon="groups" /> <TabList className="border-b-0">
PAM <Link
</div> to="/projects/pam/$projectId/accounts"
<div className="flex-1"> params={{
<Menu> projectId: currentProject.id
<MenuGroup title="Resources"> }}
<Link >
to="/projects/pam/$projectId/accounts" {({ isActive }) => <Tab value={isActive ? "selected" : ""}>Accounts</Tab>}
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faUser} />
</div>
Accounts
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/pam/$projectId/resources"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBoxOpen} />
</div>
Resources
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/pam/$projectId/sessions"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faDisplay} />
</div>
Sessions
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Others">
<Link
to="/projects/pam/$projectId/access-management"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faUsers} />
</div>
Project Access
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/pam/$projectId/audit-logs"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBook} />
</div>
Audit Logs
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/pam/$projectId/settings"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCog} />
</div>
Project Settings
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
</div>
<div>
<Menu>
<Link to="/organization/projects">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
</div>
}
>
Organization Home
</MenuItem>
</Link> </Link>
</Menu> <Link
</div> to="/projects/pam/$projectId/resources"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Resources</Tab>}
</Link>
<Link
to="/projects/pam/$projectId/sessions"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Sessions</Tab>}
</Link>
<Link
to="/projects/pam/$projectId/access-management"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab
value={
isActive ||
location.pathname.match(/\/groups\/|\/identities\/|\/members\/|\/roles\//)
? "selected"
: ""
}
>
Access Control
</Tab>
)}
</Link>
<Link
to="/projects/pam/$projectId/audit-logs"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Audit Logs</Tab>}
</Link>
<Link
to="/projects/pam/$projectId/settings"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Settings</Tab>}
</Link>
</TabList>
</Tabs>
</nav> </nav>
</motion.div> </motion.div>
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 p-4 pt-8"> </div>
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />} {assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
<Outlet /> <div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4">
</div> <Outlet />
</div> </div>
</div> </div>
<UpgradePlanModal <UpgradePlanModal
@@ -1,9 +1,7 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { faArrowLeft, 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 { Link, Outlet } from "@tanstack/react-router"; import { Outlet } from "@tanstack/react-router";
import { WishForm } from "@app/components/features/WishForm";
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner"; import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
@@ -12,27 +10,10 @@ export const PersonalSettingsLayout = () => {
return ( return (
<> <>
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex"> <div className="dark hidden h-screen w-full flex-col overflow-x-hidden bg-bunker-800 md:flex">
{!window.isSecureContext && <InsecureConnectionBanner />} {!window.isSecureContext && <InsecureConnectionBanner />}
<div className="flex grow flex-col overflow-y-hidden md:flex-row"> <div className="flex grow flex-col overflow-y-hidden md:flex-row">
<aside className="dark w-full border-r border-mineshaft-600 bg-linear-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"> <main className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4 dark:scheme-dark">
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:scheme-dark">
<div className="grow">
<Link to="/organization/projects">
<div className="my-6 flex cursor-default items-center justify-center pr-2 text-sm text-mineshaft-300 hover:text-mineshaft-100">
<FontAwesomeIcon icon={faArrowLeft} className="pr-3" />
Back to organization
</div>
</Link>
</div>
<div className="relative mt-10 flex w-full cursor-default flex-col items-center px-3 text-sm text-mineshaft-400">
{(window.location.origin.includes("https://app.infisical.com") ||
window.location.origin.includes("https://gamma.infisical.com")) && <WishForm />}
</div>
)
</nav>
</aside>
<main className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 dark:scheme-dark">
<Outlet /> <Outlet />
</main> </main>
</div> </div>
@@ -1,23 +1,10 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { faMobile } from "@fortawesome/free-solid-svg-icons";
faBell,
faBook,
faCertificate,
faCog,
faFileLines,
faHome,
faMobile,
faPlug,
faPuzzlePiece,
faSitemap,
faStamp,
faUsers
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router"; import { Link, Outlet, useLocation } from "@tanstack/react-router";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2"; import { Tab, TabList, Tabs } from "@app/components/v2";
import { useProject, useProjectPermission } from "@app/context"; import { useProject, useProjectPermission } from "@app/context";
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner"; import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
@@ -27,223 +14,139 @@ export const PkiManagerLayout = () => {
const { assumedPrivilegeDetails } = useProjectPermission(); const { assumedPrivilegeDetails } = useProjectPermission();
const { t } = useTranslation(); const { t } = useTranslation();
const location = useLocation();
return ( return (
<> <>
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex"> <div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="flex grow flex-col overflow-y-hidden md:flex-row"> <div className="border-b border-mineshaft-600 bg-mineshaft-900">
<motion.div <motion.div
key="menu-project-items" key="menu-project-items"
initial={{ x: -150 }} initial={{ x: -150 }}
animate={{ x: 0 }} animate={{ x: 0 }}
exit={{ x: -150 }} exit={{ x: -150 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="dark w-full border-r border-mineshaft-600 bg-linear-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60" className="px-4"
> >
<nav className="items-between flex h-full flex-col overflow-y-auto dark:scheme-dark"> <nav className="w-full">
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white"> <Tabs value="selected">
<Lottie className="inline-block h-5 w-5 shrink-0" icon="note" /> <TabList className="border-b-0">
PKI Manager <Link
</div> to="/projects/cert-management/$projectId/subscribers"
<div className="flex-1"> params={{
<Menu> projectId: currentProject.id
<MenuGroup title="Resources"> }}
<Link >
to="/projects/cert-management/$projectId/subscribers" {({ isActive }) => <Tab value={isActive ? "selected" : ""}>Subscribers</Tab>}
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faSitemap} />
</div>
Subscribers
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/certificate-templates"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faFileLines} />
</div>
Certificate Templates
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/certificates"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCertificate} />
</div>
Certificates
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/certificate-authorities"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faStamp} />
</div>
Certificates Authority
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/alerting"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBell} />
</div>
Alerting
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/integrations"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faPuzzlePiece} />
</div>
Integrations
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/app-connections"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faPlug} />
</div>
App Connections
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Others">
<Link
to="/projects/cert-management/$projectId/access-management"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faUsers} />
</div>
Project Access
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/audit-logs"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBook} />
</div>
Audit Logs
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/settings"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCog} />
</div>
Project Settings
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
</div>
<div>
<Menu>
<Link to="/organization/projects">
<MenuItem
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
</div>
}
>
Organization Home
</MenuItem>
</Link> </Link>
</Menu> <Link
</div> to="/projects/cert-management/$projectId/certificate-templates"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab value={isActive ? "selected" : ""}>Certificate Templates</Tab>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/certificates"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab
value={
isActive || location.pathname.match(/\/pki-collections\//)
? "selected"
: ""
}
>
Certificates
</Tab>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/certificate-authorities"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab value={isActive || location.pathname.match(/\/ca\//) ? "selected" : ""}>
Certificate Authorities
</Tab>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/alerting"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Alerting</Tab>}
</Link>
<Link
to="/projects/cert-management/$projectId/integrations"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Integrations</Tab>}
</Link>
<Link
to="/projects/cert-management/$projectId/app-connections"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab value={isActive ? "selected" : ""}>App Connections</Tab>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/access-management"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab
value={
isActive ||
location.pathname.match(/\/groups\/|\/identities\/|\/members\/|\/roles\//)
? "selected"
: ""
}
>
Access Control
</Tab>
)}
</Link>
<Link
to="/projects/cert-management/$projectId/audit-logs"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Audit Logs</Tab>}
</Link>
<Link
to="/projects/cert-management/$projectId/settings"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Settings</Tab>}
</Link>
</TabList>
</Tabs>
</nav> </nav>
</motion.div> </motion.div>
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 p-4 pt-8"> </div>
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />} {assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
<Outlet /> <div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4">
</div> <Outlet />
</div> </div>
</div> </div>
<div className="z-200 flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden"> <div className="z-200 flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden">
@@ -15,7 +15,7 @@ export const AssumePrivilegeModeBanner = () => {
if (!assumedPrivilegeDetails) return null; if (!assumedPrivilegeDetails) return null;
return ( return (
<div className="z-10 -mx-4 flex items-center justify-center gap-2 rounded-sm border border-mineshaft-600 bg-primary-400 p-2 text-mineshaft-800 shadow-sm"> <div className="flex w-full items-center border-b border-yellow/50 bg-yellow/30 px-4 py-2 text-sm text-yellow-200">
<div> <div>
<FontAwesomeIcon icon={faInfoCircle} className="mr-2" /> <FontAwesomeIcon icon={faInfoCircle} className="mr-2" />
You are currently viewing the project with privileges of{" "} You are currently viewing the project with privileges of{" "}
@@ -24,7 +24,7 @@ export const AssumePrivilegeModeBanner = () => {
{assumedPrivilegeDetails?.actorName} {assumedPrivilegeDetails?.actorName}
</b> </b>
</div> </div>
<div> <div className="ml-auto">
<Button <Button
size="xs" size="xs"
variant="outline_bg" variant="outline_bg"
@@ -35,10 +35,19 @@ import {
import { getProjectHomePage } from "@app/helpers/project"; import { getProjectHomePage } from "@app/helpers/project";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useGetUserProjects } from "@app/hooks/api"; import { useGetUserProjects } from "@app/hooks/api";
import { Project } from "@app/hooks/api/projects/types"; import { Project, ProjectType } from "@app/hooks/api/projects/types";
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation"; import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries"; import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
const PROJECT_TYPE_NAME: Record<ProjectType, string> = {
[ProjectType.SecretManager]: "Secrets Management",
[ProjectType.CertificateManager]: "PKI",
[ProjectType.SSH]: "SSH",
[ProjectType.KMS]: "KMS",
[ProjectType.PAM]: "PAM",
[ProjectType.SecretScanning]: "Secret Scanning"
};
export const ProjectSelect = () => { export const ProjectSelect = () => {
const [searchProject, setSearchProject] = useState(""); const [searchProject, setSearchProject] = useState("");
const { currentProject: currentWorkspace } = useProject(); const { currentProject: currentWorkspace } = useProject();
@@ -99,26 +108,24 @@ export const ProjectSelect = () => {
}, [projects, projectFavorites, currentWorkspace]); }, [projects, projectFavorites, currentWorkspace]);
return ( return (
<div className="-mr-2 flex w-full items-center gap-1"> <div className="mr-2 flex items-center gap-1 overflow-hidden">
<DropdownMenu modal={false}> <DropdownMenu modal={false}>
<Link <Link
to={getProjectHomePage(currentWorkspace.type, currentWorkspace.environments)} to={getProjectHomePage(currentWorkspace.type, currentWorkspace.environments)}
params={{ params={{
projectId: currentWorkspace.id projectId: currentWorkspace.id
}} }}
className="group flex cursor-pointer items-center gap-x-1.5 overflow-hidden hover:text-white"
> >
<div className="relative flex cursor-pointer items-center gap-2 text-sm text-white duration-100 hover:text-primary"> <p className="inline-block truncate text-mineshaft-200 group-hover:underline">
<Tooltip content={currentWorkspace.name} className="max-w-96 break-words"> {currentWorkspace?.name}
<Badge </p>
variant="project" <Badge variant="project" className="cursor-pointer">
className="max-w-44 overflow-hidden text-sm text-ellipsis whitespace-nowrap" <FontAwesomeIcon icon={faCube} />
> <span>
<FontAwesomeIcon icon={faCube} /> {currentWorkspace.type ? PROJECT_TYPE_NAME[currentWorkspace.type] : "Project"}
</span>
{currentWorkspace?.name} </Badge>
</Badge>
</Tooltip>
</div>
</Link> </Link>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<div> <div>
@@ -1,21 +1,10 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import { faMobile } from "@fortawesome/free-solid-svg-icons";
faArrowsSpin,
faBook,
faCheckToSlot,
faCog,
faHome,
faMobile,
faPlug,
faPuzzlePiece,
faUsers,
faVault
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet, useLocation } from "@tanstack/react-router"; import { Link, Outlet, useLocation } from "@tanstack/react-router";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Badge, Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2"; import { Badge, Tab, TabList, Tabs } from "@app/components/v2";
import { useProject, useProjectPermission } from "@app/context"; import { useProject, useProjectPermission } from "@app/context";
import { import {
useGetAccessRequestsCount, useGetAccessRequestsCount,
@@ -28,10 +17,10 @@ import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePri
export const SecretManagerLayout = () => { export const SecretManagerLayout = () => {
const { currentProject, projectId } = useProject(); const { currentProject, projectId } = useProject();
const { assumedPrivilegeDetails } = useProjectPermission(); const { assumedPrivilegeDetails } = useProjectPermission();
const location = useLocation();
const { t } = useTranslation(); const { t } = useTranslation();
const projectSlug = currentProject?.slug || ""; const projectSlug = currentProject?.slug || "";
const location = useLocation();
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({
projectId projectId
@@ -54,208 +43,131 @@ export const SecretManagerLayout = () => {
return ( return (
<> <>
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex"> <div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="flex grow flex-col overflow-y-hidden md:flex-row"> <div className="border-b border-mineshaft-600 bg-mineshaft-900">
<motion.div <motion.div
key="menu-project-items" key="menu-project-items"
initial={{ x: -150 }} initial={{ x: -150 }}
animate={{ x: 0 }} animate={{ x: 0 }}
exit={{ x: -150 }} exit={{ x: -150 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="dark w-full border-r border-mineshaft-600 bg-linear-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60" className="px-4"
> >
<nav className="items-between flex h-full flex-col overflow-y-auto dark:scheme-dark"> <nav className="w-full">
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white"> <Tabs value="selected">
<Lottie className="inline-block h-5 w-5 shrink-0" icon="vault" /> <TabList className="border-b-0">
Secrets Manager <Link
</div> to="/projects/secret-management/$projectId/overview"
<div className="flex-1"> params={{
<Menu> projectId: currentProject.id,
<MenuGroup title="Resources"> ...(currentProject.environments.length
<Link ? { envSlug: currentProject.environments[0]?.slug }
to="/projects/secret-management/$projectId/overview" : {})
params={{ }}
projectId: currentProject.id, >
...(currentProject.environments.length {({ isActive }) => (
? { envSlug: currentProject.environments[0]?.slug } <Tab
: {}) value={
}} isActive || location.pathname.match(/\/secrets\/|\/commits\//)
> ? "selected"
{({ isActive }) => ( : ""
<MenuItem }
variant="project"
isSelected={
isActive ||
location.pathname.startsWith(
`/projects/secret-management/${currentProject.id}/overview`
)
}
>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faVault} />
</div>
Secrets
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-management/$projectId/integrations"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faPuzzlePiece} />
</div>
Integrations
</div>
</MenuItem>
)}
</Link>
{Boolean(secretRotations?.length) && (
<Link
to="/projects/secret-management/$projectId/secret-rotation"
params={{
projectId: currentProject.id
}}
> >
{({ isActive }) => ( Overview
<MenuItem variant="project" isSelected={isActive}> </Tab>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faArrowsSpin} />
</div>
Secret Rotations
</div>
</MenuItem>
)}
</Link>
)} )}
<Link
to="/projects/secret-management/$projectId/approval"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCheckToSlot} />
</div>
Approvals
{Boolean(
secretApprovalReqCount?.open ||
accessApprovalRequestCount?.pendingCount
) && (
<Badge variant="primary" className="ml-1.5">
{pendingRequestsCount}
</Badge>
)}
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-management/$projectId/app-connections"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faPlug} />
</div>
App Connections
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Others">
<Link
to="/projects/secret-management/$projectId/access-management"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faUsers} />
</div>
Project Access
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-management/$projectId/audit-logs"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBook} />
</div>
Audit Logs
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-management/$projectId/settings"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCog} />
</div>
Project Settings
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
</div>
<div>
<Menu>
<Link to="/organization/projects">
<MenuItem
variant="project"
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
</div>
}
>
Organization Home
</MenuItem>
</Link> </Link>
</Menu> <Link
</div> to="/projects/secret-management/$projectId/approval"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab value={isActive ? "selected" : ""}>
Approvals
{Boolean(
secretApprovalReqCount?.open || accessApprovalRequestCount?.pendingCount
) && (
<Badge variant="primary" className="ml-1.5">
{pendingRequestsCount}
</Badge>
)}
</Tab>
)}
</Link>
<Link
to="/projects/secret-management/$projectId/integrations"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Integrations</Tab>}
</Link>
{Boolean(secretRotations?.length) && (
<Link
to="/projects/secret-management/$projectId/secret-rotation"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab value={isActive ? "selected" : ""}>Secret Rotations</Tab>
)}
</Link>
)}
<Link
to="/projects/secret-management/$projectId/app-connections"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab value={isActive ? "selected" : ""}>App Connections</Tab>
)}
</Link>
<Link
to="/projects/secret-management/$projectId/access-management"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab
value={
isActive ||
location.pathname.match(/\/groups\/|\/identities\/|\/members\/|\/roles\//)
? "selected"
: ""
}
>
Access Control
</Tab>
)}
</Link>
<Link
to="/projects/secret-management/$projectId/audit-logs"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Audit Logs</Tab>}
</Link>
<Link
to="/projects/secret-management/$projectId/settings"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Settings</Tab>}
</Link>
</TabList>
</Tabs>
</nav> </nav>
</motion.div> </motion.div>
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 p-4 pt-8"> </div>
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />} {assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
<Outlet /> <div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4">
</div> <Outlet />
</div> </div>
</div> </div>
<div className="z-200 flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden"> <div className="z-200 flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden">
@@ -1,17 +1,7 @@
import { import { Link, Outlet, useLocation } from "@tanstack/react-router";
faBook,
faCog,
faDatabase,
faHome,
faMagnifyingGlass,
faPlug,
faUsers
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Badge, Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2"; import { Badge, Tab, TabList, Tabs } from "@app/components/v2";
import { import {
ProjectPermissionSub, ProjectPermissionSub,
useProject, useProject,
@@ -29,6 +19,7 @@ export const SecretScanningLayout = () => {
const { permission } = useProjectPermission(); const { permission } = useProjectPermission();
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const location = useLocation();
const { data: unresolvedFindings } = useGetSecretScanningUnresolvedFindingCount( const { data: unresolvedFindings } = useGetSecretScanningUnresolvedFindingCount(
currentProject.id, currentProject.id,
@@ -45,158 +36,94 @@ export const SecretScanningLayout = () => {
return ( return (
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex"> <div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="flex grow flex-col overflow-y-hidden md:flex-row"> <div className="border-b border-mineshaft-600 bg-mineshaft-900">
<motion.div <motion.div
key="menu-project-items" key="menu-project-items"
initial={{ x: -150 }} initial={{ x: -150 }}
animate={{ x: 0 }} animate={{ x: 0 }}
exit={{ x: -150 }} exit={{ x: -150 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="dark w-full border-r border-mineshaft-600 bg-linear-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60" className="px-4"
> >
<nav className="items-between flex h-full flex-col overflow-y-auto dark:scheme-dark"> <nav className="w-full">
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white"> <Tabs value="selected">
<Lottie className="inline-block h-5 w-5 shrink-0" icon="secret-scan" /> <TabList className="border-b-0">
Secret Scanning <Link
</div> to="/projects/secret-scanning/$projectId/data-sources"
<div className="flex-1"> params={{
<Menu> projectId: currentProject.id
<MenuGroup title="Resources"> }}
<Link >
to="/projects/secret-scanning/$projectId/data-sources" {({ isActive }) => <Tab value={isActive ? "selected" : ""}>Data Sources</Tab>}
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faDatabase} />
</div>
Data Sources
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-scanning/$projectId/findings"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex w-full gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faMagnifyingGlass} />
</div>
<span>Findings</span>
{Boolean(unresolvedFindings) && (
<Badge variant="primary" className="mr-2 ml-auto h-min">
{unresolvedFindings}
</Badge>
)}
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-scanning/$projectId/app-connections"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faPlug} />
</div>
App Connections
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
<MenuGroup title="Others">
<Link
to="/projects/secret-scanning/$projectId/access-management"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faUsers} />
</div>
Project Access
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-scanning/$projectId/audit-logs"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBook} />
</div>
Audit Logs
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/secret-scanning/$projectId/settings"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCog} />
</div>
Project Settings
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
</div>
<div>
<Menu>
<Link to="/organization/projects">
<MenuItem
variant="project"
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
</div>
}
>
Organization Home
</MenuItem>
</Link> </Link>
</Menu> <Link
</div> to="/projects/secret-scanning/$projectId/findings"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab value={isActive ? "selected" : ""}>
Findings
{Boolean(unresolvedFindings) && (
<Badge variant="primary" className="ml-2 h-min">
{unresolvedFindings}
</Badge>
)}
</Tab>
)}
</Link>
<Link
to="/projects/secret-scanning/$projectId/app-connections"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>App Connections</Tab>}
</Link>
<Link
to="/projects/secret-scanning/$projectId/access-management"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab
value={
isActive ||
location.pathname.match(/\/groups\/|\/identities\/|\/members\/|\/roles\//)
? "selected"
: ""
}
>
Access Control
</Tab>
)}
</Link>
<Link
to="/projects/secret-scanning/$projectId/audit-logs"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Audit Logs</Tab>}
</Link>
<Link
to="/projects/secret-scanning/$projectId/settings"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Settings</Tab>}
</Link>
</TabList>
</Tabs>
</nav> </nav>
</motion.div> </motion.div>
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 p-4 pt-8"> </div>
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />} {assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
<Outlet /> <div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4">
</div> <Outlet />
</div> </div>
</div> </div>
); );
+87 -141
View File
@@ -1,17 +1,8 @@
import { import { Link, Outlet, useLocation } from "@tanstack/react-router";
faBook,
faCog,
faHome,
faServer,
faStamp,
faUsers
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2"; import { Tab, TabList, Tabs } from "@app/components/v2";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
@@ -24,149 +15,104 @@ import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePri
export const SshLayout = () => { export const SshLayout = () => {
const { currentProject } = useProject(); const { currentProject } = useProject();
const { assumedPrivilegeDetails } = useProjectPermission(); const { assumedPrivilegeDetails } = useProjectPermission();
const location = useLocation();
return ( return (
<div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex"> <div className="dark hidden h-full w-full flex-col overflow-x-hidden md:flex">
<div className="flex grow flex-col overflow-y-hidden md:flex-row"> <div className="border-b border-mineshaft-600 bg-mineshaft-900">
<motion.div <motion.div
key="menu-project-items" key="menu-project-items"
initial={{ x: -150 }} initial={{ x: -150 }}
animate={{ x: 0 }} animate={{ x: 0 }}
exit={{ x: -150 }} exit={{ x: -150 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="dark w-full border-r border-mineshaft-600 bg-linear-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60" className="px-4"
> >
<nav className="items-between flex h-full flex-col overflow-y-auto dark:scheme-dark"> <nav className="w-full">
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white"> <Tabs value="selected">
<Lottie className="inline-block h-5 w-5 shrink-0" icon="terminal" /> <TabList className="border-b-0">
SSH <Link
</div> to="/projects/ssh/$projectId/overview"
<div className="flex-1"> params={{
<Menu> projectId: currentProject.id
<MenuGroup title="Resources"> }}
<Link >
to="/projects/ssh/$projectId/overview" {({ isActive }) => (
params={{ <Tab
projectId: currentProject.id value={
}} isActive || location.pathname.match(/\/ssh-host-groups\//) ? "selected" : ""
> }
{({ isActive }) => ( >
<MenuItem variant="project" isSelected={isActive}> Hosts
<div className="mx-1 flex gap-2"> </Tab>
<div className="w-6"> )}
<FontAwesomeIcon icon={faServer} />
</div>
Hosts
</div>
</MenuItem>
)}
</Link>
<ProjectPermissionCan
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.SshCertificateAuthorities}
>
{(isAllowed) =>
isAllowed && (
<Link
to="/projects/ssh/$projectId/cas"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faStamp} />
</div>
Certificates Authority
</div>
</MenuItem>
)}
</Link>
)
}
</ProjectPermissionCan>
</MenuGroup>
<MenuGroup title="Others">
<Link
to="/projects/ssh/$projectId/access-management"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faUsers} />
</div>
Project Access
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/ssh/$projectId/audit-logs"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faBook} />
</div>
Audit Logs
</div>
</MenuItem>
)}
</Link>
<Link
to="/projects/ssh/$projectId/settings"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<MenuItem variant="project" isSelected={isActive}>
<div className="mx-1 flex gap-2">
<div className="w-6">
<FontAwesomeIcon icon={faCog} />
</div>
Project Settings
</div>
</MenuItem>
)}
</Link>
</MenuGroup>
</Menu>
</div>
<div>
<Menu>
<Link to="/organization/projects">
<MenuItem
variant="project"
className="relative flex items-center gap-2 overflow-hidden text-sm text-mineshaft-400 hover:text-mineshaft-300"
leftIcon={
<div className="w-6">
<FontAwesomeIcon className="mx-1 inline-block shrink-0" icon={faHome} />
</div>
}
>
Organization Home
</MenuItem>
</Link> </Link>
</Menu> <ProjectPermissionCan
</div> I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.SshCertificateAuthorities}
>
{(isAllowed) =>
isAllowed && (
<Link
to="/projects/ssh/$projectId/cas"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab
value={isActive || location.pathname.match(/\/ca\//) ? "selected" : ""}
>
Certificate Authorities
</Tab>
)}
</Link>
)
}
</ProjectPermissionCan>
<Link
to="/projects/ssh/$projectId/access-management"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => (
<Tab
value={
isActive ||
location.pathname.match(/\/groups\/|\/identities\/|\/members\/|\/roles\//)
? "selected"
: ""
}
>
Access Control
</Tab>
)}
</Link>
<Link
to="/projects/ssh/$projectId/audit-logs"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Audit Logs</Tab>}
</Link>
<Link
to="/projects/ssh/$projectId/settings"
params={{
projectId: currentProject.id
}}
>
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Settings</Tab>}
</Link>
</TabList>
</Tabs>
</nav> </nav>
</motion.div> </motion.div>
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 p-4 pt-8"> </div>
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />} {assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
<Outlet /> <div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4">
</div> <Outlet />
</div> </div>
</div> </div>
); );
@@ -13,8 +13,8 @@ export const AccessManagementPage = () => {
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "Access Control" })}</title> <title>{t("common.head-title", { title: "Access Control" })}</title>
</Helmet> </Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="instance" scope="instance"
title="Access Control" title="Access Control"
@@ -13,8 +13,8 @@ export const AuthenticationPage = () => {
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "Admin" })}</title> <title>{t("common.head-title", { title: "Admin" })}</title>
</Helmet> </Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="instance" scope="instance"
title="Authentication" title="Authentication"
@@ -13,8 +13,8 @@ export const CachingPage = () => {
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "Admin" })}</title> <title>{t("common.head-title", { title: "Admin" })}</title>
</Helmet> </Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="instance" scope="instance"
title="Caching" title="Caching"
@@ -13,8 +13,8 @@ export const EncryptionPage = () => {
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "Admin" })}</title> <title>{t("common.head-title", { title: "Admin" })}</title>
</Helmet> </Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="instance" scope="instance"
title="Encryption" title="Encryption"
@@ -13,8 +13,8 @@ export const EnvironmentPage = () => {
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "Admin" })}</title> <title>{t("common.head-title", { title: "Admin" })}</title>
</Helmet> </Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="instance" scope="instance"
title="Environment Variables" title="Environment Variables"
@@ -15,8 +15,8 @@ export const GeneralPage = () => {
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "Admin" })}</title> <title>{t("common.head-title", { title: "Admin" })}</title>
</Helmet> </Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="instance" scope="instance"
title="General" title="General"
@@ -13,8 +13,8 @@ export const IntegrationsPage = () => {
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "Admin" })}</title> <title>{t("common.head-title", { title: "Admin" })}</title>
</Helmet> </Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="instance" scope="instance"
title="Integrations" title="Integrations"
@@ -13,18 +13,24 @@ export const ResourceOverviewPage = () => {
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "Resource Overview" })}</title> <title>{t("common.head-title", { title: "Resource Overview" })}</title>
</Helmet> </Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="instance" scope="instance"
title="Resource Overview" title="Resource Overview"
description="Manage resources within your Infisical instance." description="Manage resources within your Infisical instance."
/> />
<Tabs defaultValue="tab-organizations"> <Tabs orientation="vertical" defaultValue="tab-organizations">
<TabList> <TabList>
<Tab value="tab-organizations">Organizations</Tab> <Tab variant="instance" value="tab-organizations">
<Tab value="tab-users">Users</Tab> Organizations
<Tab value="tab-identities">Identities</Tab> </Tab>
<Tab variant="instance" value="tab-users">
Users
</Tab>
<Tab variant="instance" value="tab-identities">
Identities
</Tab>
</TabList> </TabList>
<TabPanel value="tab-organizations"> <TabPanel value="tab-organizations">
<OrganizationsTable /> <OrganizationsTable />
@@ -4,19 +4,20 @@ import { useTranslation } from "react-i18next";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader } from "@app/components/v2"; import { PageHeader } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { ProjectType } from "@app/hooks/api/projects/types";
import { PkiAlertsSection } from "./components"; import { PkiAlertsSection } from "./components";
export const AlertingPage = () => { export const AlertingPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white">
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "Alerting" })}</title> <title>{t("common.head-title", { title: "Alerting" })}</title>
</Helmet> </Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="project" scope={ProjectType.CertificateManager}
title="Alerting" title="Alerting"
description="Configure alerts for expiring certificates and CAs to maintain security and compliance." description="Configure alerts for expiring certificates and CAs to maintain security and compliance."
/> />
@@ -1,5 +1,7 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useNavigate, useParams } from "@tanstack/react-router"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
@@ -18,6 +20,7 @@ import { ROUTE_PATHS } from "@app/const/routes";
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
import { CaType, useDeleteCa, useGetCa } from "@app/hooks/api"; import { CaType, useDeleteCa, useGetCa } from "@app/hooks/api";
import { TInternalCertificateAuthority } from "@app/hooks/api/ca/types"; import { TInternalCertificateAuthority } from "@app/hooks/api/ca/types";
import { ProjectType } from "@app/hooks/api/projects/types";
import { usePopUp } from "@app/hooks/usePopUp"; import { usePopUp } from "@app/hooks/usePopUp";
import { CaInstallCertModal } from "../CertificateAuthoritiesPage/components/CaInstallCertModal"; import { CaInstallCertModal } from "../CertificateAuthoritiesPage/components/CaInstallCertModal";
@@ -84,10 +87,24 @@ const Page = () => {
}; };
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && ( {data && (
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader scope="project" title={data.name}> <Link
to="/projects/cert-management/$projectId/certificate-authorities"
params={{
projectId
}}
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Certificate Authorities
</Link>
<PageHeader
scope={ProjectType.CertificateManager}
description="Manage certificate authority"
title={data.name}
>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg"> <DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400"> <div className="hover:text-primary-400 data-[state=open]:text-primary-400">
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader } from "@app/components/v2"; import { PageHeader } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { ProjectType } from "@app/hooks/api/projects/types";
import { ExternalCaSection } from "./components/ExternalCaSection"; import { ExternalCaSection } from "./components/ExternalCaSection";
import { CaSection } from "./components"; import { CaSection } from "./components";
@@ -11,13 +12,13 @@ import { CaSection } from "./components";
export const CertificateAuthoritiesPage = () => { export const CertificateAuthoritiesPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white">
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "Certificate Authorities" })}</title> <title>{t("common.head-title", { title: "Certificate Authorities" })}</title>
</Helmet> </Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="project" scope={ProjectType.CertificateManager}
title="Certificate Authorities" title="Certificate Authorities"
description="Manage certificate authorities for issuing and signing certificates" description="Manage certificate authorities for issuing and signing certificates"
/> />
@@ -9,6 +9,7 @@ import {
ProjectPermissionSub, ProjectPermissionSub,
useProjectPermission useProjectPermission
} from "@app/context"; } from "@app/context";
import { ProjectType } from "@app/hooks/api/projects/types";
import { PkiCollectionSection } from "../AlertingPage/components"; import { PkiCollectionSection } from "../AlertingPage/components";
import { CertificatesSection } from "./components"; import { CertificatesSection } from "./components";
@@ -27,13 +28,13 @@ export const CertificatesPage = () => {
); );
return ( return (
<div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white">
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "Certificates" })}</title> <title>{t("common.head-title", { title: "Certificates" })}</title>
</Helmet> </Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="project" scope={ProjectType.CertificateManager}
title="Certificates" title="Certificates"
description="View and track issued certificates, monitor expiration dates, and manage certificate lifecycles." description="View and track issued certificates, monitor expiration dates, and manage certificate lifecycles."
/> />
@@ -7,6 +7,7 @@ import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes"; import { ROUTE_PATHS } from "@app/const/routes";
import { ProjectPermissionSub, useProject } from "@app/context"; import { ProjectPermissionSub, useProject } from "@app/context";
import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types"; import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types";
import { ProjectType } from "@app/hooks/api/projects/types";
import { IntegrationsListPageTabs } from "@app/types/integrations"; import { IntegrationsListPageTabs } from "@app/types/integrations";
import { PkiSyncsTab } from "./components"; import { PkiSyncsTab } from "./components";
@@ -42,16 +43,18 @@ export const IntegrationsListPage = () => {
<meta property="og:title" content="Manage your certificates in seconds" /> <meta property="og:title" content="Manage your certificates in seconds" />
<meta name="og:description" content="Sync and manage PKI certificates across services" /> <meta name="og:description" content="Sync and manage PKI certificates across services" />
</Helmet> </Helmet>
<div className="relative container mx-auto max-w-7xl pb-12 text-white"> <div className="relative mx-auto max-w-8xl pb-12 text-white">
<div className="mb-8"> <div className="mb-8">
<PageHeader <PageHeader
scope="project" scope={ProjectType.CertificateManager}
title="Integrations" title="Integrations"
description="Manage integrations with third-party certificate services." description="Manage integrations with third-party certificate services."
/> />
<Tabs value={currentTab} onValueChange={updateSelectedTab}> <Tabs orientation="vertical" value={currentTab} onValueChange={updateSelectedTab}>
<TabList> <TabList>
<Tab value={IntegrationsListPageTabs.PkiSyncs}>Certificate Syncs</Tab> <Tab variant="project" value={IntegrationsListPageTabs.PkiSyncs}>
Certificate Syncs
</Tab>
</TabList> </TabList>
<TabPanel value={IntegrationsListPageTabs.PkiSyncs}> <TabPanel value={IntegrationsListPageTabs.PkiSyncs}>
<ProjectPermissionCan <ProjectPermissionCan
@@ -1,6 +1,8 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "@tanstack/react-router"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
@@ -19,6 +21,7 @@ import { ROUTE_PATHS } from "@app/const/routes";
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
import { useDeletePkiCollection, useGetPkiCollectionById } from "@app/hooks/api"; import { useDeletePkiCollection, useGetPkiCollectionById } from "@app/hooks/api";
import { PkiItemType } from "@app/hooks/api/pkiCollections/constants"; import { PkiItemType } from "@app/hooks/api/pkiCollections/constants";
import { ProjectType } from "@app/hooks/api/projects/types";
import { usePopUp } from "@app/hooks/usePopUp"; import { usePopUp } from "@app/hooks/usePopUp";
import { PkiCollectionModal } from "../AlertingPage/components/PkiCollectionModal"; import { PkiCollectionModal } from "../AlertingPage/components/PkiCollectionModal";
@@ -70,10 +73,24 @@ export const PkiCollectionPage = () => {
}; };
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && ( {data && (
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader scope="project" title={data.name}> <Link
to="/projects/cert-management/$projectId/certificates"
params={{
projectId
}}
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Certificates
</Link>
<PageHeader
scope={ProjectType.CertificateManager}
title={data.name}
description="Manage certificate collection"
>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg"> <DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400"> <div className="hover:text-primary-400 data-[state=open]:text-primary-400">
@@ -1,6 +1,8 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "@tanstack/react-router"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
@@ -22,6 +24,7 @@ import {
useProject useProject
} from "@app/context"; } from "@app/context";
import { useDeletePkiSubscriber, useGetPkiSubscriber } from "@app/hooks/api"; import { useDeletePkiSubscriber, useGetPkiSubscriber } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/projects/types";
import { usePopUp } from "@app/hooks/usePopUp"; import { usePopUp } from "@app/hooks/usePopUp";
import { PkiSubscriberModal } from "../PkiSubscribersPage/components/PkiSubscriberModal"; import { PkiSubscriberModal } from "../PkiSubscribersPage/components/PkiSubscriberModal";
@@ -75,10 +78,24 @@ const Page = () => {
}; };
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && ( {data && (
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader scope="project" title={data.name}> <Link
to="/projects/cert-management/$projectId/subscribers"
params={{
projectId
}}
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Subscribers
</Link>
<PageHeader
scope={ProjectType.CertificateManager}
title={data.name}
description="Manage PKI subscriber"
>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg"> <DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400"> <div className="hover:text-primary-400 data-[state=open]:text-primary-400">
@@ -2,6 +2,7 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { PageHeader } from "@app/components/v2"; import { PageHeader } from "@app/components/v2";
import { ProjectType } from "@app/hooks/api/projects/types";
import { PkiSubscriberSection } from "./components"; import { PkiSubscriberSection } from "./components";
@@ -13,10 +14,10 @@ export const PkiSubscribersPage = () => {
<title>{t("common.head-title", { title: "PKI Subscribers" })}</title> <title>{t("common.head-title", { title: "PKI Subscribers" })}</title>
</Helmet> </Helmet>
<div className="h-full bg-bunker-800"> <div className="h-full bg-bunker-800">
<div className="container mx-auto flex flex-col justify-between text-white"> <div className="mx-auto flex flex-col justify-between text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="project" scope={ProjectType.CertificateManager}
title="Subscribers" title="Subscribers"
description="Manage subscribers that request and receive certificates, including user devices, servers, and services." description="Manage subscribers that request and receive certificates, including user devices, servers, and services."
/> />
@@ -68,8 +68,8 @@ const PageContent = () => {
return ( return (
<> <>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 font-inter text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 font-inter text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<Button <Button
variant="link" variant="link"
type="submit" type="submit"
@@ -48,6 +48,7 @@ import {
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useDeleteCertTemplateV2 } from "@app/hooks/api"; import { useDeleteCertTemplateV2 } from "@app/hooks/api";
import { useListCertificateTemplates } from "@app/hooks/api/certificateTemplates/queries"; import { useListCertificateTemplates } from "@app/hooks/api/certificateTemplates/queries";
import { ProjectType } from "@app/hooks/api/projects/types";
import { CertificateTemplateEnrollmentModal } from "../CertificatesPage/components/CertificateTemplateEnrollmentModal"; import { CertificateTemplateEnrollmentModal } from "../CertificatesPage/components/CertificateTemplateEnrollmentModal";
import { PkiTemplateForm } from "./components/PkiTemplateForm"; import { PkiTemplateForm } from "./components/PkiTemplateForm";
@@ -103,15 +104,15 @@ export const PkiTemplateListPage = () => {
<title>{t("common.head-title", { title: "PKI Templates" })}</title> <title>{t("common.head-title", { title: "PKI Templates" })}</title>
</Helmet> </Helmet>
<div className="h-full bg-bunker-800"> <div className="h-full bg-bunker-800">
<div className="container mx-auto flex flex-col justify-between text-white"> <div className="mx-auto flex flex-col justify-between text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="project" scope={ProjectType.CertificateManager}
title="Certificate Templates" title="Certificate Templates"
description="Manage certificate template to request and issue dynamic certificates following a strict format." description="Manage certificate template to request and issue dynamic certificates following a strict format."
/> />
</div> </div>
<div className="container mx-auto mb-6 max-w-7xl rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> <div className="container mx-auto mb-6 max-w-8xl rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex justify-between"> <div className="mb-4 flex justify-between">
<p className="text-xl font-medium text-mineshaft-100">Templates</p> <p className="text-xl font-medium text-mineshaft-100">Templates</p>
<div className="flex w-full justify-end"> <div className="flex w-full justify-end">
@@ -267,7 +268,7 @@ export const PkiTemplateListPage = () => {
onDeleteApproved={() => onRemovePkiSubscriberSubmit()} onDeleteApproved={() => onRemovePkiSubscriberSubmit()}
/> />
</div> </div>
<div className="container mx-auto max-w-7xl" /> <div className="container mx-auto max-w-8xl" />
</div> </div>
<Modal <Modal
isOpen={popUp?.certificateTemplate?.isOpen} isOpen={popUp?.certificateTemplate?.isOpen}
@@ -2,6 +2,7 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectType } from "@app/hooks/api/projects/types";
import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab"; import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab";
const tabs = [ const tabs = [
@@ -20,12 +21,12 @@ export const SettingsPage = () => {
<Helmet> <Helmet>
<title>{t("common.head-title", { title: t("settings.project.title") })}</title> <title>{t("common.head-title", { title: t("settings.project.title") })}</title>
</Helmet> </Helmet>
<div className="w-full max-w-7xl"> <div className="w-full max-w-8xl">
<PageHeader scope="project" title={t("settings.project.title")} /> <PageHeader scope={ProjectType.CertificateManager} title={t("settings.project.title")} />
<Tabs defaultValue={tabs[0].key}> <Tabs orientation="vertical" defaultValue={tabs[0].key}>
<TabList> <TabList>
{tabs.map((tab) => ( {tabs.map((tab) => (
<Tab value={tab.key} key={tab.key}> <Tab value={tab.key} variant="project" key={tab.key}>
{tab.name} {tab.name}
</Tab> </Tab>
))} ))}
+4 -3
View File
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader } from "@app/components/v2"; import { PageHeader } from "@app/components/v2";
import { ProjectPermissionKmipActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionKmipActions, ProjectPermissionSub } from "@app/context";
import { ProjectType } from "@app/hooks/api/projects/types";
import { KmipClientTable } from "./components/KmipClientTable"; import { KmipClientTable } from "./components/KmipClientTable";
@@ -15,10 +16,10 @@ export const KmipPage = () => {
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "KMS" })}</title> <title>{t("common.head-title", { title: "KMS" })}</title>
</Helmet> </Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="project" scope={ProjectType.KMS}
title="KMIP" title="KMIP"
description="Integrate with Infisical KMS via Key Management Interoperability Protocol." description="Integrate with Infisical KMS via Key Management Interoperability Protocol."
/> />
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader } from "@app/components/v2"; import { PageHeader } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { ProjectType } from "@app/hooks/api/projects/types";
import { CmekTable } from "./components"; import { CmekTable } from "./components";
@@ -15,10 +16,10 @@ export const OverviewPage = () => {
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "KMS" })}</title> <title>{t("common.head-title", { title: "KMS" })}</title>
</Helmet> </Helmet>
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="project" scope={ProjectType.KMS}
title="Overview" title="Overview"
description="Manage keys and perform cryptographic operations." description="Manage keys and perform cryptographic operations."
/> />
@@ -2,6 +2,7 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectType } from "@app/hooks/api/projects/types";
import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab"; import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab";
const tabs = [ const tabs = [
@@ -20,12 +21,16 @@ export const SettingsPage = () => {
<Helmet> <Helmet>
<title>{t("common.head-title", { title: t("settings.project.title") })}</title> <title>{t("common.head-title", { title: t("settings.project.title") })}</title>
</Helmet> </Helmet>
<div className="w-full max-w-7xl"> <div className="w-full max-w-8xl">
<PageHeader scope="project" title="Settings" /> <PageHeader
<Tabs defaultValue={tabs[0].key}> scope={ProjectType.KMS}
title="Settings"
description="Configure general project settings"
/>
<Tabs orientation="vertical" defaultValue={tabs[0].key}>
<TabList> <TabList>
{tabs.map((tab) => ( {tabs.map((tab) => (
<Tab value={tab.key} key={tab.key}> <Tab variant="project" value={tab.key} key={tab.key}>
{tab.name} {tab.name}
</Tab> </Tab>
))} ))}
@@ -76,11 +76,11 @@ export const AccessManagementPage = () => {
const hasNoAccess = tabSections.every((tab) => tab.isHidden); const hasNoAccess = tabSections.every((tab) => tab.isHidden);
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<Helmet> <Helmet>
<title>{t("common.head-title", { title: t("settings.org.title") })}</title> <title>{t("common.head-title", { title: t("settings.org.title") })}</title>
</Helmet> </Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="org" scope="org"
title="Access Control" title="Access Control"
@@ -111,7 +111,7 @@ export const AccessManagementPage = () => {
isOpen={isUpgradePrivilegeSystemModalOpen} isOpen={isUpgradePrivilegeSystemModalOpen}
onOpenChange={setIsUpgradePrivilegeSystemModalOpen} onOpenChange={setIsUpgradePrivilegeSystemModalOpen}
/> />
<Tabs value={selectedTab} onValueChange={updateSelectedTab}> <Tabs orientation="vertical" value={selectedTab} onValueChange={updateSelectedTab}>
<TabList> <TabList>
{tabSections {tabSections
.filter((el) => !el.isHidden) .filter((el) => !el.isHidden)
@@ -1,17 +1,5 @@
import { motion } from "framer-motion";
import { OrgGroupsSection } from "./components"; import { OrgGroupsSection } from "./components";
export const OrgGroupsTab = () => { export const OrgGroupsTab = () => {
return ( return <OrgGroupsSection />;
<motion.div
key="panel-org-groups"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<OrgGroupsSection />
</motion.div>
);
}; };
@@ -1,17 +1,5 @@
import { motion } from "framer-motion";
import { IdentitySection } from "./components"; import { IdentitySection } from "./components";
export const OrgIdentityTab = () => { export const OrgIdentityTab = () => {
return ( return <IdentitySection />;
<motion.div
key="panel-service-token"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<IdentitySection />
</motion.div>
);
}; };
@@ -1,17 +1,5 @@
import { motion } from "framer-motion";
import { OrgMembersSection } from "./components"; import { OrgMembersSection } from "./components";
export const OrgMembersTab = () => { export const OrgMembersTab = () => {
return ( return <OrgMembersSection />;
<motion.div
key="panel-org-members"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<OrgMembersSection />
</motion.div>
);
}; };
@@ -1,17 +1,5 @@
import { motion } from "framer-motion";
import { OrgRoleTable } from "./OrgRoleTable"; import { OrgRoleTable } from "./OrgRoleTable";
export const OrgRoleTabSection = () => { export const OrgRoleTabSection = () => {
return ( return <OrgRoleTable />;
<motion.div
key="role-list"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<OrgRoleTable />
</motion.div>
);
}; };
@@ -20,7 +20,7 @@ export const AppConnectionsPage = withPermission(
<meta property="og:image" content="/images/message.png" /> <meta property="og:image" content="/images/message.png" />
</Helmet> </Helmet>
<div className="flex w-full justify-center bg-bunker-800 text-white"> <div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl"> <div className="w-full max-w-8xl">
<PageHeader <PageHeader
scope="org" scope="org"
className="w-full" className="w-full"
@@ -12,9 +12,8 @@ export const AuditLogsPage = () => {
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" /> <meta property="og:image" content="/images/message.png" />
</Helmet> </Helmet>
<div className="flex w-full justify-center bg-bunker-800 pb-6 text-white">
<div className="flex h-full w-full justify-center bg-bunker-800 text-white"> <div className="w-full max-w-8xl">
<div className="w-full max-w-7xl">
<PageHeader <PageHeader
scope="org" scope="org"
title="Audit Logs" title="Audit Logs"
@@ -18,7 +18,7 @@ export const BillingPage = () => {
<meta property="og:image" content="/images/message.png" /> <meta property="og:image" content="/images/message.png" />
</Helmet> </Helmet>
<div className="flex h-full w-full justify-center bg-bunker-800 text-white"> <div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl"> <div className="w-full max-w-8xl">
<PageHeader <PageHeader
scope="org" scope="org"
title={t("billing.title")} title={t("billing.title")}
@@ -17,15 +17,15 @@ const tabs = [
export const BillingTabGroup = withPermission( export const BillingTabGroup = withPermission(
() => { () => {
const tabsFiltered = isInfisicalCloud() if (!isInfisicalCloud()) {
? tabs return <BillingCloudTab />;
: [{ name: "Infisical Self-Hosted", key: "tab-infisical-cloud" }]; }
return ( return (
<Tabs defaultValue={tabs[0].key}> <Tabs orientation="vertical" defaultValue={tabs[0].key}>
<TabList> <TabList>
{tabsFiltered.map((tab) => ( {tabs.map((tab) => (
<Tab variant="org" value={tab.key}> <Tab variant="org" key={tab.key} value={tab.key}>
{tab.name} {tab.name}
</Tab> </Tab>
))} ))}
@@ -33,19 +33,15 @@ export const BillingTabGroup = withPermission(
<TabPanel value={tabs[0].key}> <TabPanel value={tabs[0].key}>
<BillingCloudTab /> <BillingCloudTab />
</TabPanel> </TabPanel>
{isInfisicalCloud() && ( <TabPanel value={tabs[1].key}>
<> <BillingSelfHostedTab />
<TabPanel value={tabs[1].key}> </TabPanel>
<BillingSelfHostedTab /> <TabPanel value={tabs[2].key}>
</TabPanel> <BillingReceiptsTab />
<TabPanel value={tabs[2].key}> </TabPanel>
<BillingReceiptsTab /> <TabPanel value={tabs[3].key}>
</TabPanel> <BillingDetailsTab />
<TabPanel value={tabs[3].key}> </TabPanel>
<BillingDetailsTab />
</TabPanel>
</>
)}
</Tabs> </Tabs>
); );
}, },
@@ -1,6 +1,8 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "@tanstack/react-router"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
@@ -80,10 +82,20 @@ const Page = () => {
if (isPending) return <Spinner size="sm" className="mt-2 ml-2" />; if (isPending) return <Spinner size="sm" className="mt-2 ml-2" />;
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && ( {data && (
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto w-full max-w-8xl">
<PageHeader scope="org" title={data.group.name}> <Link
to="/organization/access-management"
search={{
selectedTab: TabSections.Groups
}}
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Groups
</Link>
<PageHeader scope="org" description="Organization Group" title={data.group.name}>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg"> <DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400"> <div className="hover:text-primary-400 data-[state=open]:text-primary-400">
@@ -1,6 +1,8 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "@tanstack/react-router"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate, useParams } from "@tanstack/react-router";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
@@ -72,10 +74,20 @@ const Page = () => {
}; };
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && ( {data && (
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto w-full max-w-8xl">
<PageHeader scope="org" title={data.identity.name} /> <Link
to="/organization/access-management"
search={{
selectedTab: OrgAccessControlTabSections.Identities
}}
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Identities
</Link>
<PageHeader scope="org" description="Organization Identity" title={data.identity.name} />
<div className="flex"> <div className="flex">
<div className="mr-4 w-96"> <div className="mr-4 w-96">
<IdentityDetailsSection identityId={identityId} handlePopUpOpen={handlePopUpOpen} /> <IdentityDetailsSection identityId={identityId} handlePopUpOpen={handlePopUpOpen} />
@@ -12,7 +12,7 @@ export const NetworkingPage = () => {
<meta property="og:image" content="/images/message.png" /> <meta property="og:image" content="/images/message.png" />
</Helmet> </Helmet>
<div className="flex w-full justify-center bg-bunker-800 text-white"> <div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl"> <div className="w-full max-w-8xl">
<PageHeader <PageHeader
scope="org" scope="org"
title="Networking" title="Networking"
@@ -19,7 +19,7 @@ export const NetworkingTabGroup = () => {
const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key); const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key);
return ( return (
<Tabs value={selectedTab} onValueChange={setSelectedTab}> <Tabs orientation="vertical" value={selectedTab} onValueChange={setSelectedTab}>
<TabList> <TabList>
{tabs.map((tab) => ( {tabs.map((tab) => (
<Tab variant="org" value={tab.key} key={tab.key}> <Tab variant="org" value={tab.key} key={tab.key}>
@@ -58,18 +58,16 @@ export const ProjectsPage = () => {
: true; : true;
return ( return (
<div className="mx-auto flex max-w-7xl flex-col justify-start bg-bunker-800"> <div className="mx-auto flex max-w-8xl 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" />
</Helmet> </Helmet>
<div className="mb-4 flex flex-col items-start justify-start"> <PageHeader
<PageHeader scope="org"
scope="org" title="Overview"
title="Overview" description="Your team's complete security toolkit - organized and ready when you need them."
description="Your team's complete security toolkit - organized and ready when you need them." />
/>
</div>
{projectListView === ProjectListView.MyProjects ? ( {projectListView === ProjectListView.MyProjects ? (
<MyProjectView <MyProjectView
onAddNewProject={() => handlePopUpOpen("addNewWs")} onAddNewProject={() => handlePopUpOpen("addNewWs")}
@@ -1,8 +1,8 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { faCopy, faEllipsisV } from "@fortawesome/free-solid-svg-icons"; import { faChevronLeft, faCopy, faEllipsisV } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useParams } from "@tanstack/react-router"; import { Link, useNavigate, useParams } from "@tanstack/react-router";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
@@ -77,20 +77,26 @@ export const Page = () => {
const isCustomRole = !["admin", "member", "no-access"].includes(data?.slug ?? ""); const isCustomRole = !["admin", "member", "no-access"].includes(data?.slug ?? "");
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && ( {data && (
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto w-full max-w-8xl">
<Link
to="/organization/access-management"
search={{
selectedTab: OrgAccessControlTabSections.Roles
}}
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Roles
</Link>
<PageHeader <PageHeader
scope="org" scope="org"
title={ title={data.name}
<div className="flex flex-col"> description={
<div> <>
<span>{data.name}</span> {data.slug} {data.description && `- ${data.description}`}
<p className="text-sm leading-3 font-normal text-mineshaft-400 normal-case"> </>
{data.slug} {data.description && `- ${data.description}`}
</p>
</div>
</div>
} }
> >
{isCustomRole && ( {isCustomRole && (
@@ -20,7 +20,7 @@ export const SecretSharingPage = () => {
<meta name="og:description" content={String(t("approval.og-description"))} /> <meta name="og:description" content={String(t("approval.og-description"))} />
</Helmet> </Helmet>
<div className="h-full"> <div className="h-full">
<div className="container mx-auto h-full w-full max-w-7xl bg-bunker-800 text-white"> <div className="mx-auto h-full w-full max-w-8xl bg-bunker-800 text-white">
<PageHeader <PageHeader
scope="org" scope="org"
title="Secret Sharing" title="Secret Sharing"
@@ -1,7 +1,7 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useNavigate, useSearch } from "@tanstack/react-router"; import { useNavigate, useSearch } from "@tanstack/react-router";
import { Badge, Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes"; import { ROUTE_PATHS } from "@app/const/routes";
import { RequestSecretTab } from "./components/RequestSecret/RequestSecretTab"; import { RequestSecretTab } from "./components/RequestSecret/RequestSecretTab";
@@ -34,14 +34,13 @@ export const ShareSecretSection = () => {
<meta property="og:image" content="/images/message.png" /> <meta property="og:image" content="/images/message.png" />
</Helmet> </Helmet>
<Tabs value={selectedTab} onValueChange={updateSelectedTab}> <Tabs orientation="vertical" value={selectedTab} onValueChange={updateSelectedTab}>
<TabList> <TabList>
<Tab value={SecretSharingPageTabs.ShareSecret}>Share Secrets</Tab> <Tab variant="org" value={SecretSharingPageTabs.ShareSecret}>
<Tab value={SecretSharingPageTabs.RequestSecret}> Share Secrets
</Tab>
<Tab variant="org" value={SecretSharingPageTabs.RequestSecret}>
Request Secrets Request Secrets
<Badge variant="primary" className="ml-1 cursor-pointer text-xs">
New
</Badge>
</Tab> </Tab>
</TabList> </TabList>
<TabPanel value={SecretSharingPageTabs.ShareSecret}> <TabPanel value={SecretSharingPageTabs.ShareSecret}>
@@ -20,7 +20,7 @@ export const SecretSharingSettingsPage = withPermission(
<title>{t("common.head-title", { title: "Secret Share Settings" })}</title> <title>{t("common.head-title", { title: "Secret Share Settings" })}</title>
</Helmet> </Helmet>
<div className="flex w-full justify-center bg-bunker-800 text-white"> <div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl"> <div className="w-full max-w-8xl">
<PageHeader scope="org" title="Secret Share Settings" /> <PageHeader scope="org" title="Secret Share Settings" />
<SecretSharingSettingsTabGroup /> <SecretSharingSettingsTabGroup />
</div> </div>
@@ -14,8 +14,12 @@ export const SettingsPage = () => {
<title>{t("common.head-title", { title: t("settings.org.title") })}</title> <title>{t("common.head-title", { title: t("settings.org.title") })}</title>
</Helmet> </Helmet>
<div className="flex w-full justify-center bg-bunker-800 text-white"> <div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl"> <div className="w-full max-w-8xl">
<PageHeader scope="org" title={t("settings.org.title")} /> <PageHeader
scope="org"
description="Configure organization-wide settings"
title={t("settings.org.title")}
/>
<OrgTabGroup /> <OrgTabGroup />
</div> </div>
</div> </div>
@@ -55,7 +55,7 @@ export const OrgTabGroup = () => {
const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key); const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key);
return ( return (
<Tabs value={selectedTab} onValueChange={setSelectedTab}> <Tabs orientation="vertical" value={selectedTab} onValueChange={setSelectedTab}>
<TabList> <TabList>
{tabs.map((tab) => ( {tabs.map((tab) => (
<Tab variant="org" value={tab.key} key={tab.key}> <Tab variant="org" value={tab.key} key={tab.key}>
@@ -1,6 +1,8 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "@tanstack/react-router"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
@@ -115,16 +117,30 @@ const Page = withPermission(
}; };
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{membership && ( {membership && (
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto w-full max-w-8xl">
<Link
to="/organization/access-management"
search={{
selectedTab: OrgAccessControlTabSections.Member
}}
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Users
</Link>
<PageHeader <PageHeader
scope="org" scope="org"
title={ title={
membership.user.firstName || membership.user.lastName membership.user.firstName || membership.user.lastName
? `${membership.user.firstName} ${membership.user.lastName ?? ""}`.trim() ? `${membership.user.firstName} ${membership.user.lastName ?? ""}`.trim()
: "-" : (membership.user.username ??
membership.user.email ??
membership.inviteEmail ??
"Unknown User")
} }
description="Organization User Membership"
> >
<div> <div>
{userId !== membership.user.id && ( {userId !== membership.user.id && (
@@ -5,6 +5,7 @@ import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader } from "@app/components/v2"; import { PageHeader } from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSub } from "@app/context";
import { ProjectPermissionPamAccountActions } from "@app/context/ProjectPermissionContext/types"; import { ProjectPermissionPamAccountActions } from "@app/context/ProjectPermissionContext/types";
import { ProjectType } from "@app/hooks/api/projects/types";
import { PamAccountsSection } from "./components/PamAccountsSection"; import { PamAccountsSection } from "./components/PamAccountsSection";
@@ -21,10 +22,10 @@ export const PamAccountsPage = () => {
a={ProjectPermissionSub.PamAccounts} a={ProjectPermissionSub.PamAccounts}
> >
<div className="h-full bg-bunker-800"> <div className="h-full bg-bunker-800">
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="project" scope={ProjectType.PAM}
title="Accounts" title="Accounts"
description="View, access, and manage accounts." description="View, access, and manage accounts."
/> />
@@ -0,0 +1,67 @@
import { faFolderOpen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router";
type Props = {
path: string;
};
export const FolderBreadCrumbs = ({ path = "/" }: Props) => {
const navigate = useNavigate({
from: "/projects/pam/$projectId/accounts"
});
const onFolderCrumbClick = (index: number) => {
let newAccountPath = `/${path.split("/").filter(Boolean).slice(0, index).join("/")}`;
if (!newAccountPath.endsWith("/")) {
newAccountPath += "/";
}
if (path === newAccountPath) return;
navigate({
search: (prev) => ({ ...prev, accountPath: newAccountPath })
});
};
return (
<div className="flex items-center space-x-2">
<div
className="breadcrumb relative z-20 border-solid border-mineshaft-600 bg-mineshaft-800 py-1 pr-2 pl-5 text-sm hover:bg-mineshaft-600"
onClick={() => onFolderCrumbClick(0)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onFolderCrumbClick(0);
}
}}
role="button"
tabIndex={0}
>
<FontAwesomeIcon icon={faFolderOpen} className="text-primary-700" />
</div>
{(path || "")
.split("/")
.filter(Boolean)
.map((pathSegment, index, arr) => (
<div
key={`path-${index + 1}`}
className={`breadcrumb relative z-20 ${
index + 1 === arr.length ? "cursor-default" : "cursor-pointer"
} border-solid border-mineshaft-600 py-1 pr-2 pl-5 text-sm text-mineshaft-200`}
onClick={() => onFolderCrumbClick(index + 1)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onFolderCrumbClick(index + 1);
}
}}
role="button"
tabIndex={0}
>
{pathSegment}
</div>
))}
</div>
);
};
@@ -45,6 +45,7 @@ import { OrderByDirection } from "@app/hooks/api/generic/types";
import { PAM_RESOURCE_TYPE_MAP, TPamAccount, TPamFolder } from "@app/hooks/api/pam"; import { PAM_RESOURCE_TYPE_MAP, TPamAccount, TPamFolder } from "@app/hooks/api/pam";
import { AccountView, AccountViewToggle } from "./AccountViewToggle"; import { AccountView, AccountViewToggle } from "./AccountViewToggle";
import { FolderBreadCrumbs } from "./FolderBreadCrumbs";
import { PamAccessAccountModal } from "./PamAccessAccountModal"; import { PamAccessAccountModal } from "./PamAccessAccountModal";
import { PamAccountRow } from "./PamAccountRow"; import { PamAccountRow } from "./PamAccountRow";
import { PamAddAccountModal } from "./PamAddAccountModal"; import { PamAddAccountModal } from "./PamAddAccountModal";
@@ -255,6 +256,7 @@ export const PamAccountsTable = ({ accounts, folders, projectId }: Props) => {
return ( return (
<div> <div>
{accountView === AccountView.Nested && <FolderBreadCrumbs path={accountPath} />}
<div className="mt-4 flex gap-2"> <div className="mt-4 flex gap-2">
<ProjectPermissionCan I={ProjectPermissionActions.Read} a={ProjectPermissionSub.PamFolders}> <ProjectPermissionCan I={ProjectPermissionActions.Read} a={ProjectPermissionSub.PamFolders}>
{(isAllowed) => {(isAllowed) =>
@@ -5,6 +5,7 @@ import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader } from "@app/components/v2"; import { PageHeader } from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSub } from "@app/context";
import { ProjectPermissionPamAccountActions } from "@app/context/ProjectPermissionContext/types"; import { ProjectPermissionPamAccountActions } from "@app/context/ProjectPermissionContext/types";
import { ProjectType } from "@app/hooks/api/projects/types";
import { PamResourcesSection } from "./components/PamResourcesSection"; import { PamResourcesSection } from "./components/PamResourcesSection";
@@ -21,10 +22,10 @@ export const PamResourcesPage = () => {
a={ProjectPermissionSub.PamResources} a={ProjectPermissionSub.PamResources}
> >
<div className="h-full bg-bunker-800"> <div className="h-full bg-bunker-800">
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="project" scope={ProjectType.PAM}
title="Resources" title="Resources"
description="Manage resources such as servers, databases, and more." description="Manage resources such as servers, databases, and more."
/> />
@@ -1,12 +1,15 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useParams } from "@tanstack/react-router"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useParams } from "@tanstack/react-router";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader } from "@app/components/v2"; import { PageHeader } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes"; import { ROUTE_PATHS } from "@app/const/routes";
import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSub, useProject } from "@app/context";
import { ProjectPermissionPamSessionActions } from "@app/context/ProjectPermissionContext/types"; import { ProjectPermissionPamSessionActions } from "@app/context/ProjectPermissionContext/types";
import { useGetPamSessionById } from "@app/hooks/api/pam"; import { useGetPamSessionById } from "@app/hooks/api/pam";
import { ProjectType } from "@app/hooks/api/projects/types";
import { PamSessionDetailsSection } from "./components/PamSessionDetailsSection"; import { PamSessionDetailsSection } from "./components/PamSessionDetailsSection";
import { PamSessionLogsSection } from "./components/PamSessionLogsSection"; import { PamSessionLogsSection } from "./components/PamSessionLogsSection";
@@ -17,13 +20,23 @@ const Page = () => {
select: (el) => el.sessionId select: (el) => el.sessionId
}); });
const { data: session } = useGetPamSessionById(sessionId); const { data: session } = useGetPamSessionById(sessionId);
const { currentProject } = useProject();
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{session && ( {session && (
<div className="mx-auto mb-6 flex w-full max-w-7xl flex-col"> <div className="mx-auto mb-6 flex w-full max-w-8xl flex-col">
<Link
to="/projects/pam/$projectId/sessions"
params={{
projectId: currentProject.id
}}
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Sessions
</Link>
<PageHeader <PageHeader
scope="project" scope={ProjectType.PAM}
title={`${session.accountName} Session`} title={`${session.accountName} Session`}
description={`View details for this ${session.accountName} session.`} description={`View details for this ${session.accountName} session.`}
/> />
@@ -5,6 +5,7 @@ import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader } from "@app/components/v2"; import { PageHeader } from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionSub } from "@app/context";
import { ProjectPermissionPamSessionActions } from "@app/context/ProjectPermissionContext/types"; import { ProjectPermissionPamSessionActions } from "@app/context/ProjectPermissionContext/types";
import { ProjectType } from "@app/hooks/api/projects/types";
import { PamSessionSection } from "./components/PamSessionSection"; import { PamSessionSection } from "./components/PamSessionSection";
@@ -21,10 +22,10 @@ export const PamSessionPage = () => {
a={ProjectPermissionSub.PamSessions} a={ProjectPermissionSub.PamSessions}
> >
<div className="h-full bg-bunker-800"> <div className="h-full bg-bunker-800">
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="project" scope={ProjectType.PAM}
title="Sessions" title="Sessions"
description="Filter and search through account sessions." description="Filter and search through account sessions."
/> />
@@ -2,6 +2,7 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ProjectType } from "@app/hooks/api/projects/types";
import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab"; import { ProjectGeneralTab } from "@app/pages/project/SettingsPage/components/ProjectGeneralTab";
export const SettingsPage = () => { export const SettingsPage = () => {
@@ -12,11 +13,17 @@ export const SettingsPage = () => {
<Helmet> <Helmet>
<title>{t("common.head-title", { title: t("settings.project.title") })}</title> <title>{t("common.head-title", { title: t("settings.project.title") })}</title>
</Helmet> </Helmet>
<div className="w-full max-w-7xl"> <div className="w-full max-w-8xl">
<PageHeader scope="project" title="Settings" description="Configure your PAM project." /> <PageHeader
<Tabs defaultValue="tab-project-general"> scope={ProjectType.PAM}
title="Settings"
description="Configure your PAM project."
/>
<Tabs orientation="vertical" defaultValue="tab-project-general">
<TabList> <TabList>
<Tab value="tab-project-general">General</Tab> <Tab variant="project" value="tab-project-general">
General
</Tab>
</TabList> </TabList>
<TabPanel value="tab-project-general"> <TabPanel value="tab-project-general">
<ProjectGeneralTab /> <ProjectGeneralTab />
@@ -37,26 +37,32 @@ const Page = () => {
const isSecretManager = currentProject.type === ProjectType.SecretManager; const isSecretManager = currentProject.type === ProjectType.SecretManager;
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="project" scope={currentProject.type}
title="Access Control" title="Access Control"
description="Manage fine-grained access for users, groups, roles, and identities within your project resources." description="Manage fine-grained access for users, groups, roles, and identities within your project resources."
/> />
<Tabs value={selectedTab} onValueChange={updateSelectedTab}> <Tabs orientation="vertical" value={selectedTab} onValueChange={updateSelectedTab}>
<TabList> <TabList>
<Tab value={ProjectAccessControlTabs.Member}>Users</Tab> <Tab variant="project" value={ProjectAccessControlTabs.Member}>
<Tab value={ProjectAccessControlTabs.Groups}>Groups</Tab> Users
<Tab value={ProjectAccessControlTabs.Identities}> </Tab>
<div className="flex items-center"> <Tab variant="project" value={ProjectAccessControlTabs.Groups}>
<p>Machine Identities</p> Groups
</div> </Tab>
<Tab variant="project" value={ProjectAccessControlTabs.Identities}>
Identities
</Tab> </Tab>
{isSecretManager && ( {isSecretManager && (
<Tab value={ProjectAccessControlTabs.ServiceTokens}>Service Tokens</Tab> <Tab variant="project" value={ProjectAccessControlTabs.ServiceTokens}>
Service Tokens
</Tab>
)} )}
<Tab value={ProjectAccessControlTabs.Roles}>Project Roles</Tab> <Tab variant="project" value={ProjectAccessControlTabs.Roles}>
Roles
</Tab>
</TabList> </TabList>
<TabPanel value={ProjectAccessControlTabs.Member}> <TabPanel value={ProjectAccessControlTabs.Member}>
<MembersTab /> <MembersTab />
@@ -1,5 +1,3 @@
import { motion } from "framer-motion";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { withProjectPermission } from "@app/hoc"; import { withProjectPermission } from "@app/hoc";
@@ -7,17 +5,7 @@ import { GroupsSection } from "./components";
export const GroupsTab = withProjectPermission( export const GroupsTab = withProjectPermission(
() => { () => {
return ( return <GroupsSection />;
<motion.div
key="panel-groups"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<GroupsSection />
</motion.div>
);
}, },
{ {
action: ProjectPermissionActions.Read, action: ProjectPermissionActions.Read,
@@ -14,7 +14,6 @@ import {
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router"; import { useNavigate } from "@tanstack/react-router";
import { format } from "date-fns"; import { format } from "date-fns";
import { motion } from "framer-motion";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
@@ -158,307 +157,295 @@ export const IdentityTab = withProjectPermission(
}; };
return ( return (
<motion.div <div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
key="identity-role-panel" <div className="mb-4 flex items-center justify-between">
transition={{ duration: 0.15 }} <div className="flex items-center gap-1">
initial={{ opacity: 0, translateX: 30 }} <p className="text-xl font-medium text-mineshaft-100">Identities</p>
animate={{ opacity: 1, translateX: 0 }} <a
exit={{ opacity: 0, translateX: 30 }} href="https://infisical.com/docs/documentation/platform/identities/overview"
> target="_blank"
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"> rel="noopener noreferrer"
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-1">
<p className="text-xl font-medium text-mineshaft-100">Identities</p>
<a
href="https://infisical.com/docs/documentation/platform/identities/overview"
target="_blank"
rel="noopener noreferrer"
>
<div className="mt-[0.16rem] ml-1 inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-[10px]"
/>
</div>
</a>
</div>
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={ProjectPermissionSub.Identity}
> >
{(isAllowed) => ( <div className="mt-[0.16rem] ml-1 inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
<Button <FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
colorSchema="secondary" <span>Docs</span>
type="submit" <FontAwesomeIcon
leftIcon={<FontAwesomeIcon icon={faPlus} />} icon={faArrowUpRightFromSquare}
onClick={() => handlePopUpOpen("identity")} className="mb-[0.07rem] ml-1.5 text-[10px]"
isDisabled={!isAllowed} />
> </div>
Add Identity </a>
</Button>
)}
</ProjectPermissionCan>
</div> </div>
<Input <ProjectPermissionCan
containerClassName="mb-4" I={ProjectPermissionActions.Create}
value={search} a={ProjectPermissionSub.Identity}
onChange={(e) => setSearch(e.target.value)} >
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />} {(isAllowed) => (
placeholder="Search identities by name..." <Button
/> colorSchema="secondary"
<TableContainer> type="submit"
<Table> leftIcon={<FontAwesomeIcon icon={faPlus} />}
<THead> onClick={() => handlePopUpOpen("identity")}
<Tr className="h-14"> isDisabled={!isAllowed}
<Th className="w-1/3"> >
<div className="flex items-center"> Add Identity
Name </Button>
<IconButton )}
variant="plain" </ProjectPermissionCan>
className={`ml-2 ${ </div>
orderBy === ProjectIdentityOrderBy.Name ? "" : "opacity-30" <Input
}`} containerClassName="mb-4"
ariaLabel="sort" value={search}
onClick={() => handleSort(ProjectIdentityOrderBy.Name)} onChange={(e) => setSearch(e.target.value)}
> leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
<FontAwesomeIcon placeholder="Search identities by name..."
icon={ />
orderDirection === OrderByDirection.DESC && <TableContainer>
orderBy === ProjectIdentityOrderBy.Name <Table>
? faArrowUp <THead>
: faArrowDown <Tr className="h-14">
} <Th className="w-1/3">
/> <div className="flex items-center">
</IconButton> Name
</div> <IconButton
</Th> variant="plain"
<Th className="w-1/3">Role</Th> className={`ml-2 ${
<Th>Added on</Th> orderBy === ProjectIdentityOrderBy.Name ? "" : "opacity-30"
<Th className="w-5">{isFetching ? <Spinner size="xs" /> : null}</Th> }`}
</Tr> ariaLabel="sort"
</THead> onClick={() => handleSort(ProjectIdentityOrderBy.Name)}
<TBody> >
{isPending && <TableSkeleton columns={4} innerKey="project-identities" />} <FontAwesomeIcon
{!isPending && icon={
data && orderDirection === OrderByDirection.DESC &&
data.identityMemberships.length > 0 && orderBy === ProjectIdentityOrderBy.Name
data.identityMemberships.map((identityMember) => { ? faArrowUp
const { : faArrowDown
identity: { id, name }, }
roles, />
createdAt </IconButton>
} = identityMember; </div>
return ( </Th>
<Tr <Th className="w-1/3">Role</Th>
className="group h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700" <Th>Added on</Th>
key={`st-v3-${id}`} <Th className="w-5">{isFetching ? <Spinner size="xs" /> : null}</Th>
role="button" </Tr>
tabIndex={0} </THead>
onKeyDown={(evt) => { <TBody>
if (evt.key === "Enter") { {isPending && <TableSkeleton columns={4} innerKey="project-identities" />}
navigate({ {!isPending &&
to: `${getProjectBaseURL(currentProject.type)}/identities/$identityId` as const, data &&
params: { data.identityMemberships.length > 0 &&
projectId: currentProject.id, data.identityMemberships.map((identityMember) => {
identityId: id const {
} identity: { id, name },
}); roles,
} createdAt
}} } = identityMember;
onClick={() => return (
<Tr
className="group h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
key={`st-v3-${id}`}
role="button"
tabIndex={0}
onKeyDown={(evt) => {
if (evt.key === "Enter") {
navigate({ navigate({
to: `${getProjectBaseURL(currentProject.type)}/identities/$identityId` as const, to: `${getProjectBaseURL(currentProject.type)}/identities/$identityId` as const,
params: { params: {
projectId: currentProject.id, projectId: currentProject.id,
identityId: id identityId: id
} }
}) });
} }
> }}
<Td>{name}</Td> onClick={() =>
navigate({
to: `${getProjectBaseURL(currentProject.type)}/identities/$identityId` as const,
params: {
projectId: currentProject.id,
identityId: id
}
})
}
>
<Td>{name}</Td>
<Td> <Td>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
{roles {roles
.slice(0, MAX_ROLES_TO_BE_SHOWN_IN_TABLE) .slice(0, MAX_ROLES_TO_BE_SHOWN_IN_TABLE)
.map( .map(
({ ({
role, role,
customRoleName, customRoleName,
id: roleId, id: roleId,
isTemporary, isTemporary,
temporaryAccessEndTime temporaryAccessEndTime
}) => { }) => {
const isExpired = const isExpired =
new Date() > new Date(temporaryAccessEndTime || ("" as string)); new Date() > new Date(temporaryAccessEndTime || ("" as string));
return ( return (
<Tag key={roleId}> <Tag key={roleId}>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div className="capitalize"> <div className="capitalize">
{formatProjectRoleName(role, customRoleName)} {formatProjectRoleName(role, customRoleName)}
</div>
{isTemporary && (
<div>
<Tooltip
content={
isExpired
? "Timed role expired"
: "Timed role access"
}
>
<FontAwesomeIcon
icon={faClock}
className={twMerge(isExpired && "text-red-600")}
/>
</Tooltip>
</div>
)}
</div> </div>
</Tag> {isTemporary && (
); <div>
} <Tooltip
)} content={
{roles.length > MAX_ROLES_TO_BE_SHOWN_IN_TABLE && ( isExpired ? "Timed role expired" : "Timed role access"
<HoverCard> }
<HoverCardTrigger> >
<Tag>+{roles.length - MAX_ROLES_TO_BE_SHOWN_IN_TABLE}</Tag> <FontAwesomeIcon
</HoverCardTrigger> icon={faClock}
<HoverCardContent className="border border-gray-700 bg-mineshaft-800 p-4"> className={twMerge(isExpired && "text-red-600")}
{roles />
.slice(MAX_ROLES_TO_BE_SHOWN_IN_TABLE) </Tooltip>
.map( </div>
({ )}
role, </div>
customRoleName, </Tag>
id: roleId, );
isTemporary, }
temporaryAccessEndTime
}) => {
const isExpired =
new Date() >
new Date(temporaryAccessEndTime || ("" as string));
return (
<Tag key={roleId} className="capitalize">
<div className="flex items-center space-x-2">
<div>
{formatProjectRoleName(role, customRoleName)}
</div>
{isTemporary && (
<div>
<Tooltip
content={
isExpired
? "Access expired"
: "Temporary access"
}
>
<FontAwesomeIcon
icon={faClock}
className={twMerge(
new Date() >
new Date(
temporaryAccessEndTime as string
) && "text-red-600"
)}
/>
</Tooltip>
</div>
)}
</div>
</Tag>
);
}
)}
</HoverCardContent>
</HoverCard>
)} )}
</div> {roles.length > MAX_ROLES_TO_BE_SHOWN_IN_TABLE && (
</Td> <HoverCard>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td> <HoverCardTrigger>
<Td className="flex justify-end space-x-2"> <Tag>+{roles.length - MAX_ROLES_TO_BE_SHOWN_IN_TABLE}</Tag>
<Tooltip className="max-w-sm text-center" content="Options"> </HoverCardTrigger>
<DropdownMenu> <HoverCardContent className="border border-gray-700 bg-mineshaft-800 p-4">
<DropdownMenuTrigger asChild> {roles
<IconButton .slice(MAX_ROLES_TO_BE_SHOWN_IN_TABLE)
ariaLabel="Options" .map(
colorSchema="secondary" ({
className="w-6" role,
variant="plain" customRoleName,
> id: roleId,
<FontAwesomeIcon icon={faEllipsisV} /> isTemporary,
</IconButton> temporaryAccessEndTime
</DropdownMenuTrigger> }) => {
<DropdownMenuContent sideOffset={2} align="end"> const isExpired =
<ProjectPermissionCan new Date() >
I={ProjectPermissionActions.Delete} new Date(temporaryAccessEndTime || ("" as string));
a={subject(ProjectPermissionSub.Identity, { return (
identityId: id <Tag key={roleId} className="capitalize">
})} <div className="flex items-center space-x-2">
> <div>{formatProjectRoleName(role, customRoleName)}</div>
{(isAllowed) => ( {isTemporary && (
<DropdownMenuItem <div>
icon={<FontAwesomeIcon icon={faCircleXmark} />} <Tooltip
isDisabled={!isAllowed} content={
onClick={(evt) => { isExpired
evt.stopPropagation(); ? "Access expired"
evt.preventDefault(); : "Temporary access"
handlePopUpOpen("deleteIdentity", { }
identityId: id, >
name <FontAwesomeIcon
}); icon={faClock}
}} className={twMerge(
> new Date() >
Remove Identity From Project new Date(
</DropdownMenuItem> temporaryAccessEndTime as string
) && "text-red-600"
)}
/>
</Tooltip>
</div>
)}
</div>
</Tag>
);
}
)} )}
</ProjectPermissionCan> </HoverCardContent>
</DropdownMenuContent> </HoverCard>
</DropdownMenu> )}
</Tooltip> </div>
</Td> </Td>
</Tr> <Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
); <Td className="flex justify-end space-x-2">
})} <Tooltip className="max-w-sm text-center" content="Options">
</TBody> <DropdownMenu>
</Table> <DropdownMenuTrigger asChild>
{!isPending && data && totalCount > 0 && ( <IconButton
<Pagination ariaLabel="Options"
count={totalCount} colorSchema="secondary"
page={page} className="w-6"
perPage={perPage} variant="plain"
onChangePage={(newPage) => setPage(newPage)} >
onChangePerPage={handlePerPageChange} <FontAwesomeIcon icon={faEllipsisV} />
/> </IconButton>
)} </DropdownMenuTrigger>
{!isPending && data && data?.identityMemberships.length === 0 && ( <DropdownMenuContent sideOffset={2} align="end">
<EmptyState <ProjectPermissionCan
title={ I={ProjectPermissionActions.Delete}
debouncedSearch.trim().length > 0 a={subject(ProjectPermissionSub.Identity, {
? "No identities match search filter" identityId: id
: "No identities have been added to this project" })}
} >
icon={faServer} {(isAllowed) => (
/> <DropdownMenuItem
)} icon={<FontAwesomeIcon icon={faCircleXmark} />}
</TableContainer> isDisabled={!isAllowed}
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} /> onClick={(evt) => {
<DeleteActionModal evt.stopPropagation();
isOpen={popUp.deleteIdentity.isOpen} evt.preventDefault();
title={`Are you sure you want to remove ${ handlePopUpOpen("deleteIdentity", {
(popUp?.deleteIdentity?.data as { name: string })?.name || "" identityId: id,
} from the project?`} name
onChange={(isOpen) => handlePopUpToggle("deleteIdentity", isOpen)} });
deleteKey="confirm" }}
onDeleteApproved={() => >
onRemoveIdentitySubmit( Remove Identity From Project
(popUp?.deleteIdentity?.data as { identityId: string })?.identityId </DropdownMenuItem>
) )}
} </ProjectPermissionCan>
/> </DropdownMenuContent>
</div> </DropdownMenu>
</motion.div> </Tooltip>
</Td>
</Tr>
);
})}
</TBody>
</Table>
{!isPending && data && totalCount > 0 && (
<Pagination
count={totalCount}
page={page}
perPage={perPage}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={handlePerPageChange}
/>
)}
{!isPending && data && data?.identityMemberships.length === 0 && (
<EmptyState
title={
debouncedSearch.trim().length > 0
? "No identities match search filter"
: "No identities have been added to this project"
}
icon={faServer}
/>
)}
</TableContainer>
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal
isOpen={popUp.deleteIdentity.isOpen}
title={`Are you sure you want to remove ${
(popUp?.deleteIdentity?.data as { name: string })?.name || ""
} from the project?`}
onChange={(isOpen) => handlePopUpToggle("deleteIdentity", isOpen)}
deleteKey="confirm"
onDeleteApproved={() =>
onRemoveIdentitySubmit(
(popUp?.deleteIdentity?.data as { identityId: string })?.identityId
)
}
/>
</div>
); );
}, },
{ action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.Identity } { action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.Identity }
@@ -1,5 +1,3 @@
import { motion } from "framer-motion";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { withProjectPermission } from "@app/hoc"; import { withProjectPermission } from "@app/hoc";
@@ -7,17 +5,7 @@ import { MembersSection } from "./components";
export const MembersTab = withProjectPermission( export const MembersTab = withProjectPermission(
() => { () => {
return ( return <MembersSection />;
<motion.div
key="panel-project-members"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<MembersSection />
</motion.div>
);
}, },
{ {
action: ProjectPermissionActions.Read, action: ProjectPermissionActions.Read,
@@ -1,5 +1,3 @@
import { motion } from "framer-motion";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { withProjectPermission } from "@app/hoc"; import { withProjectPermission } from "@app/hoc";
@@ -7,17 +5,7 @@ import { ProjectRoleList } from "./components/ProjectRoleList";
export const ProjectRoleListTab = withProjectPermission( export const ProjectRoleListTab = withProjectPermission(
() => { () => {
return ( return <ProjectRoleList />;
<motion.div
key="role-list"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<ProjectRoleList />
</motion.div>
);
}, },
{ action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.Role } { action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.Role }
); );
@@ -1,20 +1,12 @@
// import { faWarning } from "@fortawesome/free-solid-svg-icons"; // import { faWarning } from "@fortawesome/free-solid-svg-icons";
// import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; // import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { motion } from "framer-motion";
import { ServiceTokenSection } from "./components"; import { ServiceTokenSection } from "./components";
export const ServiceTokenTab = () => { export const ServiceTokenTab = () => {
return ( return (
<motion.div <div className="space-y-3">
key="panel-service-token" {/* <div className="flex w-full flex-row items-center rounded-md border border-primary-600/70 bg-primary/[.07] p-4 text-base text-white">
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<div className="space-y-3">
{/* <div className="flex w-full flex-row items-center rounded-md border border-primary-600/70 bg-primary/[.07] p-4 text-base text-white">
<FontAwesomeIcon icon={faWarning} className="pr-6 text-4xl text-white/80" /> <FontAwesomeIcon icon={faWarning} className="pr-6 text-4xl text-white/80" />
<div className="flex w-full flex-col text-sm"> <div className="flex w-full flex-col text-sm">
<span className="mb-4 text-lg font-medium">Deprecation Notice</span> <span className="mb-4 text-lg font-medium">Deprecation Notice</span>
@@ -42,8 +34,7 @@ export const ServiceTokenTab = () => {
</p> </p>
</div> </div>
</div> */} </div> */}
<ServiceTokenSection /> <ServiceTokenSection />
</div> </div>
</motion.div>
); );
}; };
@@ -21,9 +21,9 @@ export const AppConnectionsPage = withProjectPermission(
<meta property="og:image" content="/images/message.png" /> <meta property="og:image" content="/images/message.png" />
</Helmet> </Helmet>
<div className="flex w-full justify-center bg-bunker-800 text-white"> <div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl"> <div className="w-full max-w-8xl">
<PageHeader <PageHeader
scope="project" scope={currentProject.type}
className="w-full" className="w-full"
title="App Connections" title="App Connections"
description="Manage project App Connections" description="Manage project App Connections"
@@ -8,15 +8,15 @@ export const AuditLogsPage = () => {
const { currentProject } = useProject(); const { currentProject } = useProject();
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<Helmet> <Helmet>
<title>Project Audit Logs</title> <title>Project Audit Logs</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
</Helmet> </Helmet>
<div className="flex h-full w-full justify-center bg-bunker-800 text-white"> <div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl"> <div className="w-full max-w-8xl">
<PageHeader <PageHeader
scope="project" scope={currentProject.type}
title="Audit logs" title="Audit logs"
description="Audit logs for security and compliance teams to monitor information access." description="Audit logs for security and compliance teams to monitor information access."
/> />
@@ -1,11 +1,16 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useParams } from "@tanstack/react-router"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useParams } from "@tanstack/react-router";
import { formatRelative } from "date-fns";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { EmptyState, PageHeader, Spinner } from "@app/components/v2"; import { EmptyState, PageHeader, Spinner } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
import { getProjectBaseURL } from "@app/helpers/project";
import { useGetWorkspaceGroupMembershipDetails } from "@app/hooks/api/projects/queries"; import { useGetWorkspaceGroupMembershipDetails } from "@app/hooks/api/projects/queries";
import { ProjectAccessControlTabs } from "@app/types/project";
import { GroupDetailsSection } from "./components/GroupDetailsSection"; import { GroupDetailsSection } from "./components/GroupDetailsSection";
import { GroupMembersSection } from "./components/GroupMembersSection"; import { GroupMembersSection } from "./components/GroupMembersSection";
@@ -31,10 +36,27 @@ const Page = () => {
); );
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{groupMembership ? ( {groupMembership ? (
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader scope="project" title={groupMembership.group.name} /> <Link
to={`${getProjectBaseURL(currentProject.type)}/access-management`}
params={{
projectId: currentProject.id
}}
search={{
selectedTab: ProjectAccessControlTabs.Groups
}}
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Groups
</Link>
<PageHeader
scope={currentProject.type}
title={groupMembership.group.name}
description={`Group joined on ${formatRelative(new Date(groupMembership.createdAt || ""), new Date())}`}
/>
<div className="flex"> <div className="flex">
<div className="mr-4 w-96"> <div className="mr-4 w-96">
<GroupDetailsSection groupMembership={groupMembership} /> <GroupDetailsSection groupMembership={groupMembership} />
@@ -1,7 +1,9 @@
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 { useNavigate, useParams } from "@tanstack/react-router"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate, useParams } from "@tanstack/react-router";
import { formatRelative } from "date-fns"; import { formatRelative } from "date-fns";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
@@ -28,6 +30,7 @@ import {
useGetWorkspaceIdentityMembershipDetails useGetWorkspaceIdentityMembershipDetails
} from "@app/hooks/api"; } from "@app/hooks/api";
import { ActorType } from "@app/hooks/api/auditLogs/enums"; import { ActorType } from "@app/hooks/api/auditLogs/enums";
import { ProjectAccessControlTabs } from "@app/types/project";
import { IdentityProjectAdditionalPrivilegeSection } from "./components/IdentityProjectAdditionalPrivilegeSection"; import { IdentityProjectAdditionalPrivilegeSection } from "./components/IdentityProjectAdditionalPrivilegeSection";
import { IdentityRoleDetailsSection } from "./components/IdentityRoleDetailsSection"; import { IdentityRoleDetailsSection } from "./components/IdentityRoleDetailsSection";
@@ -113,11 +116,24 @@ const Page = () => {
} }
return ( return (
<div className="container mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex max-w-8xl flex-col justify-between bg-bunker-800 text-white">
{identityMembershipDetails ? ( {identityMembershipDetails ? (
<> <>
<Link
to={`${getProjectBaseURL(currentProject.type)}/access-management`}
params={{
projectId
}}
search={{
selectedTab: ProjectAccessControlTabs.Identities
}}
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Identities
</Link>
<PageHeader <PageHeader
scope="project" scope={currentProject.type}
title={identityMembershipDetails?.identity?.name} title={identityMembershipDetails?.identity?.name}
description={`Identity joined on ${identityMembershipDetails?.createdAt && formatRelative(new Date(identityMembershipDetails?.createdAt || ""), new Date())}`} description={`Identity joined on ${identityMembershipDetails?.createdAt && formatRelative(new Date(identityMembershipDetails?.createdAt || ""), new Date())}`}
> >
@@ -1,6 +1,8 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "@tanstack/react-router"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate, useParams } from "@tanstack/react-router";
import { formatRelative } from "date-fns"; import { formatRelative } from "date-fns";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
@@ -29,6 +31,7 @@ import {
useGetWorkspaceUserDetails useGetWorkspaceUserDetails
} from "@app/hooks/api"; } from "@app/hooks/api";
import { ActorType } from "@app/hooks/api/auditLogs/enums"; import { ActorType } from "@app/hooks/api/auditLogs/enums";
import { ProjectAccessControlTabs } from "@app/types/project";
import { MemberProjectAdditionalPrivilegeSection } from "./components/MemberProjectAdditionalPrivilegeSection"; import { MemberProjectAdditionalPrivilegeSection } from "./components/MemberProjectAdditionalPrivilegeSection";
import { MemberRoleDetailsSection } from "./components/MemberRoleDetailsSection"; import { MemberRoleDetailsSection } from "./components/MemberRoleDetailsSection";
@@ -115,11 +118,24 @@ export const Page = () => {
} }
return ( return (
<div className="container mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex max-w-8xl flex-col justify-between bg-bunker-800 text-white">
{membershipDetails ? ( {membershipDetails ? (
<> <>
<Link
to={`${getProjectBaseURL(currentProject.type)}/access-management`}
params={{
projectId: currentProject.id
}}
search={{
selectedTab: ProjectAccessControlTabs.Member
}}
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Users
</Link>
<PageHeader <PageHeader
scope="project" scope={currentProject.type}
title={ title={
membershipDetails.user.firstName || membershipDetails.user.lastName membershipDetails.user.firstName || membershipDetails.user.lastName
? `${membershipDetails.user.firstName} ${membershipDetails.user.lastName}` ? `${membershipDetails.user.firstName} ${membershipDetails.user.lastName}`
@@ -1,8 +1,14 @@
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { faCopy, faEdit, faEllipsisV, faTrash } from "@fortawesome/free-solid-svg-icons"; import {
faChevronLeft,
faCopy,
faEdit,
faEllipsisV,
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useParams } from "@tanstack/react-router"; import { Link, useNavigate, useParams } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
@@ -86,20 +92,29 @@ const Page = () => {
); );
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && ( {data && (
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<Link
to={`${getProjectBaseURL(currentProject.type)}/access-management`}
params={{
projectId
}}
search={{
selectedTab: ProjectAccessControlTabs.Roles
}}
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Roles
</Link>
<PageHeader <PageHeader
scope="project" scope={currentProject.type}
title={ title={data.name}
<div className="flex flex-col"> description={
<div> <>
<span>{data.name}</span> {data.slug} {data.description && `- ${data.description}`}
<p className="text-sm leading-3 font-normal text-mineshaft-400 normal-case"> </>
{data.slug} {data.description && `- ${data.description}`}
</p>
</div>
</div>
} }
> >
{isCustomRole && ( {isCustomRole && (
@@ -35,7 +35,7 @@ export const DeleteProjectProtection = () => {
<p className="mb-3 text-xl font-medium">Delete Protection</p> <p className="mb-3 text-xl font-medium">Delete Protection</p>
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}> <ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
{(isAllowed) => ( {(isAllowed) => (
<div className="w-max"> <div>
<Checkbox <Checkbox
id="hasDeleteProtection" id="hasDeleteProtection"
isDisabled={!isAllowed} isDisabled={!isAllowed}
@@ -43,6 +43,7 @@ export const DeleteProjectProtection = () => {
onCheckedChange={(state) => { onCheckedChange={(state) => {
handleToggleDeleteProjectProtection(state as boolean); handleToggleDeleteProjectProtection(state as boolean);
}} }}
allowMultilineLabel
> >
Protects the project from being deleted accidentally. While this option is enabled, Protects the project from being deleted accidentally. While this option is enabled,
you can&apos;t delete the project. you can&apos;t delete the project.
@@ -17,7 +17,7 @@ export const ErrorPage = ({ error }: ErrorComponentProps) => {
} }
return ( return (
<div className="flex h-screen w-screen items-center justify-center bg-mineshaft-900"> <div className="flex h-full items-center justify-center">
<div className="flex max-w-3xl flex-col rounded-md border border-mineshaft-600 bg-mineshaft-800 p-8 text-center text-mineshaft-200"> <div className="flex max-w-3xl flex-col rounded-md border border-mineshaft-600 bg-mineshaft-800 p-8 text-center text-mineshaft-200">
<FontAwesomeIcon icon={faBugs} className="my-2 inline text-6xl" /> <FontAwesomeIcon icon={faBugs} className="my-2 inline text-6xl" />
<p> <p>
@@ -64,7 +64,7 @@ export const CommitDetailsPage = () => {
}; };
return ( return (
<div className="mx-auto flex w-full max-w-7xl justify-center bg-bunker-800 pt-2 pb-4 text-white"> <div className="mx-auto flex w-full max-w-8xl justify-center bg-bunker-800 pt-2 pb-4 text-white">
<div className="w-full max-w-[75vw]"> <div className="w-full max-w-[75vw]">
<ProjectPermissionCan <ProjectPermissionCan
renderGuardBanner renderGuardBanner
@@ -29,6 +29,7 @@ import {
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { CommitWithChanges } from "@app/hooks/api/folderCommits"; import { CommitWithChanges } from "@app/hooks/api/folderCommits";
import { useCommitRevert, useGetCommitDetails } from "@app/hooks/api/folderCommits/queries"; import { useCommitRevert, useGetCommitDetails } from "@app/hooks/api/folderCommits/queries";
import { ProjectType } from "@app/hooks/api/projects/types";
import { CommitType } from "@app/hooks/api/types"; import { CommitType } from "@app/hooks/api/types";
import { SecretVersionDiffView } from "../SecretVersionDiffView"; import { SecretVersionDiffView } from "../SecretVersionDiffView";
@@ -260,7 +261,7 @@ export const CommitDetailsTab = ({
Commit History Commit History
</Button> </Button>
<PageHeader <PageHeader
scope="project" scope={ProjectType.SecretManager}
title={`${parsedCommitDetails.changes?.message}` || "No message"} title={`${parsedCommitDetails.changes?.message}` || "No message"}
description={ description={
<> <>
@@ -24,6 +24,7 @@ import {
} from "@app/context/ProjectPermissionContext/types"; } from "@app/context/ProjectPermissionContext/types";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useCommitRollback, useGetRollbackPreview } from "@app/hooks/api/folderCommits/queries"; import { useCommitRollback, useGetRollbackPreview } from "@app/hooks/api/folderCommits/queries";
import { ProjectType } from "@app/hooks/api/projects/types";
import { SecretVersionDiffView } from "../SecretVersionDiffView"; import { SecretVersionDiffView } from "../SecretVersionDiffView";
@@ -297,7 +298,7 @@ export const RollbackPreviewTab = (): JSX.Element => {
}; };
return ( return (
<div className="mx-auto flex w-full max-w-7xl justify-center bg-bunker-800 pt-2 pb-4 text-white"> <div className="mx-auto flex w-full max-w-8xl justify-center bg-bunker-800 pt-2 pb-4 text-white">
<ProjectPermissionCan <ProjectPermissionCan
renderGuardBanner renderGuardBanner
I={ProjectPermissionCommitsActions.PerformRollback} I={ProjectPermissionCommitsActions.PerformRollback}
@@ -307,7 +308,7 @@ export const RollbackPreviewTab = (): JSX.Element => {
<div className="h-full w-full"> <div className="h-full w-full">
<div> <div>
<PageHeader <PageHeader
scope="project" scope={ProjectType.SecretManager}
title={`Restore folder at commit ${selectedCommitId.substring(0, 8)}`} title={`Restore folder at commit ${selectedCommitId.substring(0, 8)}`}
description={`Will return all changes in this folder to how they appeared at the point of commit ${selectedCommitId.substring(0, 8)}. Any modifications made after this commit will be undone.`} description={`Will return all changes in this folder to how they appeared at the point of commit ${selectedCommitId.substring(0, 8)}. Any modifications made after this commit will be undone.`}
/> />
@@ -1,4 +1,6 @@
import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate, useParams, useSearch } from "@tanstack/react-router";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader } from "@app/components/v2"; import { PageHeader } from "@app/components/v2";
@@ -9,6 +11,7 @@ import {
ProjectPermissionCommitsActions, ProjectPermissionCommitsActions,
ProjectPermissionSub ProjectPermissionSub
} from "@app/context/ProjectPermissionContext/types"; } from "@app/context/ProjectPermissionContext/types";
import { ProjectType } from "@app/hooks/api/projects/types";
import { CommitHistoryTab } from "./components/CommitHistoryTab"; import { CommitHistoryTab } from "./components/CommitHistoryTab";
@@ -46,10 +49,21 @@ export const CommitsPage = () => {
}; };
return ( return (
<div className="mx-auto flex h-full w-full max-w-7xl justify-center bg-bunker-800 py-4 text-white"> <div className="mx-auto mb-4 flex h-full w-full max-w-8xl justify-center bg-bunker-800 text-white">
<div className="w-full max-w-[75vw]"> <div className="w-full">
<Link
to="/projects/secret-management/$projectId/secrets/$envSlug"
params={{
projectId: currentProject.id,
envSlug
}}
className="mb-4 flex items-center gap-x-2 text-sm text-mineshaft-400"
>
<FontAwesomeIcon icon={faChevronLeft} />
Secrets
</Link>
<PageHeader <PageHeader
scope="project" scope={ProjectType.SecretManager}
title="Commits" title="Commits"
description="Track, inspect, and restore your secrets and folders with confidence. View the complete history of changes made to your environment, examine specific modifications at each commit point, and preview the exact impact before rolling back to previous states." description="Track, inspect, and restore your secrets and folders with confidence. View the complete history of changes made to your environment, examine specific modifications at each commit point, and preview the exact impact before rolling back to previous states."
/> />
@@ -15,7 +15,7 @@ export const IPAllowListPage = () => {
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
</Helmet> </Helmet>
<div className="flex h-full w-full justify-center bg-bunker-800 text-white"> <div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl px-6"> <div className="w-full max-w-8xl px-6">
<div className="my-6"> <div className="my-6">
<p className="text-3xl font-medium text-gray-200">IP Allowlist</p> <p className="text-3xl font-medium text-gray-200">IP Allowlist</p>
<div /> <div />
@@ -90,11 +90,11 @@ export const IntegrationDetailsByIDPage = () => {
<meta property="og:title" content="Manage your .env files in seconds" /> <meta property="og:title" content="Manage your .env files in seconds" />
<meta name="og:description" content={t("integrations.description") as string} /> <meta name="og:description" content={t("integrations.description") as string} />
</Helmet> </Helmet>
<div className="mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 text-white"> <div className="mx-auto flex max-w-8xl flex-col justify-between bg-bunker-800 text-white">
{integration ? ( {integration ? (
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-8xl">
<PageHeader <PageHeader
scope="project" scope={ProjectType.SecretManager}
title={`${integrationSlugNameMapping[integration.integration]} Integration`} title={`${integrationSlugNameMapping[integration.integration]} Integration`}
> >
<DropdownMenu> <DropdownMenu>
@@ -7,6 +7,7 @@ import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes"; import { ROUTE_PATHS } from "@app/const/routes";
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types"; import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
import { ProjectType } from "@app/hooks/api/projects/types";
import { IntegrationsListPageTabs } from "@app/types/integrations"; import { IntegrationsListPageTabs } from "@app/types/integrations";
import { import {
@@ -45,21 +46,25 @@ export const IntegrationsListPage = () => {
<meta property="og:title" content="Manage your .env files in seconds" /> <meta property="og:title" content="Manage your .env files in seconds" />
<meta name="og:description" content={t("integrations.description") as string} /> <meta name="og:description" content={t("integrations.description") as string} />
</Helmet> </Helmet>
<div className="relative container mx-auto max-w-7xl pb-12 text-white"> <div className="relative mx-auto max-w-8xl pb-12 text-white">
<div className="mb-8"> <div className="mb-8">
<PageHeader <PageHeader
scope="project" scope={ProjectType.SecretManager}
title="Integrations" title="Integrations"
description="Manage integrations with third-party services." description="Manage integrations with third-party services."
/> />
<Tabs value={selectedTab} onValueChange={updateSelectedTab}> <Tabs orientation="vertical" value={selectedTab} onValueChange={updateSelectedTab}>
<TabList> <TabList>
<Tab value={IntegrationsListPageTabs.SecretSyncs}>Secret Syncs</Tab> <Tab variant="project" value={IntegrationsListPageTabs.SecretSyncs}>
<Tab value={IntegrationsListPageTabs.NativeIntegrations}>Native Integrations</Tab> Secret Syncs
<Tab value={IntegrationsListPageTabs.FrameworkIntegrations}> </Tab>
<Tab variant="project" value={IntegrationsListPageTabs.NativeIntegrations}>
Native Integrations
</Tab>
<Tab variant="project" value={IntegrationsListPageTabs.FrameworkIntegrations}>
Framework Integrations Framework Integrations
</Tab> </Tab>
<Tab value={IntegrationsListPageTabs.InfrastructureIntegrations}> <Tab variant="project" value={IntegrationsListPageTabs.InfrastructureIntegrations}>
Infrastructure Integrations Infrastructure Integrations
</Tab> </Tab>
</TabList> </TabList>
@@ -93,7 +93,7 @@ import {
import { useGetProjectSecretsOverview } from "@app/hooks/api/dashboard/queries"; import { useGetProjectSecretsOverview } from "@app/hooks/api/dashboard/queries";
import { DashboardSecretsOrderBy, ProjectSecretsImportedBy } from "@app/hooks/api/dashboard/types"; import { DashboardSecretsOrderBy, ProjectSecretsImportedBy } from "@app/hooks/api/dashboard/types";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
import { ProjectVersion } from "@app/hooks/api/projects/types"; import { ProjectType, ProjectVersion } from "@app/hooks/api/projects/types";
import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries"; import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries";
import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types"; import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types";
import { TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2"; import { TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2";
@@ -929,11 +929,11 @@ export const OverviewPage = () => {
<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="relative mx-auto max-w-7xl text-mineshaft-50 dark:scheme-dark"> <div className="relative mx-auto max-w-8xl text-mineshaft-50 dark:scheme-dark">
<div className="flex w-full items-baseline justify-between"> <div className="flex w-full items-baseline justify-between">
<PageHeader <PageHeader
scope="project" scope={ProjectType.SecretManager}
title="Secrets Overview" title="Overview"
description={ description={
<p className="text-md text-bunker-300"> <p className="text-md text-bunker-300">
Inject your secrets using Inject your secrets using
@@ -977,7 +977,7 @@ export const OverviewPage = () => {
} }
/> />
</div> </div>
<div className="mt-4 flex items-center justify-between"> <div className="flex items-center justify-between">
<FolderBreadCrumbs secretPath={secretPath} onResetSearch={handleResetSearch} /> <FolderBreadCrumbs secretPath={secretPath} onResetSearch={handleResetSearch} />
<div className="flex flex-row items-center justify-center space-x-2"> <div className="flex flex-row items-center justify-center space-x-2">
{isTableFiltered && ( {isTableFiltered && (
@@ -46,7 +46,7 @@ export const QuickSearchDynamicSecretItem = ({
</span> </span>
<span className="text-xs text-mineshaft-400"> <span className="text-xs text-mineshaft-400">
<FontAwesomeIcon size="xs" className="mr-0.5 text-yellow-700" icon={faFolder} />{" "} <FontAwesomeIcon size="xs" className="mr-0.5 text-yellow-700" icon={faFolder} />{" "}
<Tooltip className="max-w-7xl" content={groupDynamicSecret.path}> <Tooltip className="max-w-8xl" content={groupDynamicSecret.path}>
<span>{reverseTruncate(groupDynamicSecret.path)}</span> <span>{reverseTruncate(groupDynamicSecret.path)}</span>
</Tooltip> </Tooltip>
</span> </span>
@@ -32,7 +32,7 @@ export const QuickSearchFolderItem = ({ folderGroup, onClose }: Props) => {
> >
<Td className="w-full whitespace-nowrap"> <Td className="w-full whitespace-nowrap">
<FontAwesomeIcon className="text-yellow-700" icon={faFolder} /> <FontAwesomeIcon className="text-yellow-700" icon={faFolder} />
<Tooltip content={groupFolder.path} className="max-w-7xl"> <Tooltip content={groupFolder.path} className="max-w-8xl">
<div className="ml-2 inline-block">{reverseTruncate(groupFolder.path)}</div> <div className="ml-2 inline-block">{reverseTruncate(groupFolder.path)}</div>
</Tooltip> </Tooltip>
</Td> </Td>
@@ -126,7 +126,7 @@ export const QuickSearchSecretItem = ({
</span> </span>
<span className="text-xs text-mineshaft-400"> <span className="text-xs text-mineshaft-400">
<FontAwesomeIcon size="xs" className="mr-0.5 text-yellow-700" icon={faFolder} />{" "} <FontAwesomeIcon size="xs" className="mr-0.5 text-yellow-700" icon={faFolder} />{" "}
<Tooltip className="max-w-7xl" content={groupSecret.path}> <Tooltip className="max-w-8xl" content={groupSecret.path}>
<span>{reverseTruncate(groupSecret.path ?? "")}</span> <span>{reverseTruncate(groupSecret.path ?? "")}</span>
</Tooltip> </Tooltip>
</span> </span>
@@ -42,7 +42,7 @@ export const QuickSearchSecretRotationItem = ({ secretRotationGroup, onClose }:
</span> </span>
<span className="text-xs text-mineshaft-400"> <span className="text-xs text-mineshaft-400">
<FontAwesomeIcon size="xs" className="mr-0.5 text-yellow-700" icon={faFolder} />{" "} <FontAwesomeIcon size="xs" className="mr-0.5 text-yellow-700" icon={faFolder} />{" "}
<Tooltip className="max-w-7xl" content={groupSecretRotation.folder.path}> <Tooltip className="max-w-8xl" content={groupSecretRotation.folder.path}>
<span>{reverseTruncate(groupSecretRotation.folder.path)}</span> <span>{reverseTruncate(groupSecretRotation.folder.path)}</span>
</Tooltip> </Tooltip>
</span> </span>

Some files were not shown because too many files have changed in this diff Show More