mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #4681 from Infisical/nav-restructure
improvement(frontend): Implement new navigation structure
This commit is contained in:
@@ -63,14 +63,14 @@ export const WishForm = () => {
|
||||
open={isOpen}
|
||||
>
|
||||
<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" />
|
||||
Request a feature
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
hideCloseBtn
|
||||
align="start"
|
||||
align="end"
|
||||
alignOffset={20}
|
||||
className="mb-1 w-auto border border-mineshaft-600 bg-mineshaft-900 p-4 drop-shadow-2xl"
|
||||
sticky="always"
|
||||
|
||||
@@ -20,11 +20,11 @@ const badgeVariants = cva(
|
||||
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",
|
||||
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:
|
||||
"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:
|
||||
"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;
|
||||
};
|
||||
|
||||
const BreadcrumbContainer = ({ breadcrumbs }: { breadcrumbs: TBreadcrumbFormat[] }) => (
|
||||
<div className="mx-auto max-w-7xl text-white">
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
const BreadcrumbContainer = ({
|
||||
breadcrumbs,
|
||||
className
|
||||
}: {
|
||||
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) => {
|
||||
const isNotLastCrumb = index + 1 !== breadcrumbs.length;
|
||||
const BreadcrumbSegment = isNotLastCrumb ? BreadcrumbLink : BreadcrumbPage;
|
||||
@@ -165,8 +171,8 @@ const BreadcrumbContainer = ({ breadcrumbs }: { breadcrumbs: TBreadcrumbFormat[]
|
||||
const Component = el.component;
|
||||
return (
|
||||
<React.Fragment key={`breadcrumb-group-${index + 1}`}>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbSegment>
|
||||
<BreadcrumbItem className="overflow-hidden">
|
||||
<BreadcrumbSegment className="overflow-hidden">
|
||||
<Component />
|
||||
</BreadcrumbSegment>
|
||||
</BreadcrumbItem>
|
||||
|
||||
@@ -5,29 +5,48 @@ import { ReactNode } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Badge } from "@app/components/v2";
|
||||
import { BadgeProps } from "@app/components/v2/Badge/Badge";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
type Props = {
|
||||
title: ReactNode;
|
||||
description?: ReactNode;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
scope: "org" | "project" | "namespace" | "instance";
|
||||
scope: "org" | "namespace" | "instance" | ProjectType | null;
|
||||
};
|
||||
|
||||
const SCOPE_NAME: Record<NonNullable<Props["scope"]>, { label: string; icon: IconDefinition }> = {
|
||||
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 },
|
||||
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) => (
|
||||
<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="mr-4 flex w-full items-center">
|
||||
<h1 className="text-3xl font-medium text-white capitalize">{title}</h1>
|
||||
{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} />
|
||||
{SCOPE_NAME[scope].label}
|
||||
</Badge>
|
||||
|
||||
@@ -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 { twMerge } from "tailwind-merge";
|
||||
|
||||
export type TabsProps = TabsPrimitive.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}
|
||||
</TabsPrimitive.Root>
|
||||
);
|
||||
@@ -13,7 +22,11 @@ export type TabListProps = TabsPrimitive.TabsListProps;
|
||||
|
||||
export const TabList = ({ className, children, ...props }: TabListProps) => (
|
||||
<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}
|
||||
>
|
||||
{children}
|
||||
@@ -26,18 +39,29 @@ export const Tab = ({
|
||||
className,
|
||||
children,
|
||||
variant = "project",
|
||||
icon,
|
||||
...props
|
||||
}: TabProps & { variant?: "project" | "namespace" | "org" }) => (
|
||||
}: TabProps & {
|
||||
icon?: IconDefinition;
|
||||
variant?: "project" | "namespace" | "org" | "instance";
|
||||
}) => (
|
||||
<TabsPrimitive.Trigger
|
||||
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 === "namespace" && "data-[state=active]:border-namespace-v1",
|
||||
variant === "org" && "data-[state=active]:border-org-v1",
|
||||
variant === "instance" && "data-[state=active]:border-mineshaft-300",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{icon && <FontAwesomeIcon icon={icon} className="mr-2" size="xs" />}
|
||||
{children}
|
||||
</TabsPrimitive.Trigger>
|
||||
);
|
||||
@@ -46,7 +70,10 @@ export type TabPanelProps = TabsPrimitive.TabsContentProps;
|
||||
|
||||
export const TabPanel = ({ className, children, ...props }: TabPanelProps) => (
|
||||
<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}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
--font-inter: "Inter", sans-serif;
|
||||
--color-org-v1: #30B3FF;
|
||||
--color-namespace-v1: #96ff59;
|
||||
|
||||
--max-width-8xl: 88rem; /* 1408px */
|
||||
/* Primary */
|
||||
--color-primary-50: #fffff5;
|
||||
--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 { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
|
||||
import { AdminSidebar } from "./Sidebar";
|
||||
import { AdminNavBar } from "./AdminNavBar";
|
||||
|
||||
export const AdminLayout = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -33,9 +33,9 @@ export const AdminLayout = () => {
|
||||
{!isLoading && !serverDetails?.emailConfigured && <SmtpBanner />}
|
||||
{!isLoading && subscription.auditLogs && <AuditLogBanner />}
|
||||
{!window.isSecureContext && <InsecureConnectionBanner />}
|
||||
<div className="flex grow flex-col overflow-y-hidden md:flex-row">
|
||||
<AdminSidebar />
|
||||
<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 grow flex-col overflow-y-hidden">
|
||||
<AdminNavBar />
|
||||
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4 dark:scheme-dark">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
99
frontend/src/layouts/AdminLayout/AdminNavBar.tsx
Normal file
99
frontend/src/layouts/AdminLayout/AdminNavBar.tsx
Normal file
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,7 @@
|
||||
import { faBook, faCog, faCube, faHome, faLock, faUsers } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, Outlet } from "@tanstack/react-router";
|
||||
import { Link, Outlet, useLocation } from "@tanstack/react-router";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
||||
import { Tab, TabList, Tabs } from "@app/components/v2";
|
||||
import { useProject, useProjectPermission } from "@app/context";
|
||||
|
||||
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
|
||||
@@ -12,138 +10,81 @@ export const KmsLayout = () => {
|
||||
const { currentProject } = useProject();
|
||||
const { assumedPrivilegeDetails } = useProjectPermission();
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<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
|
||||
key="menu-project-items"
|
||||
initial={{ x: -150 }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: -150 }}
|
||||
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">
|
||||
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
<Lottie className="inline-block h-5 w-5 shrink-0" icon="unlock" />
|
||||
KMS
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Menu>
|
||||
<MenuGroup title="Resources">
|
||||
<Link
|
||||
to="/projects/kms/$projectId/overview"
|
||||
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>
|
||||
<nav className="w-full">
|
||||
<Tabs value="selected">
|
||||
<TabList className="border-b-0">
|
||||
<Link
|
||||
to="/projects/kms/$projectId/overview"
|
||||
params={{
|
||||
projectId: currentProject.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Overview</Tab>}
|
||||
</Link>
|
||||
</Menu>
|
||||
</div>
|
||||
<Link
|
||||
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>
|
||||
</motion.div>
|
||||
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 p-4 pt-8">
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useFetchServerStatus } from "@app/hooks/api";
|
||||
import { AuditLogBanner } from "./components/AuditLogBanner";
|
||||
import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner";
|
||||
import { Navbar } from "./components/NavBar";
|
||||
import { OrgSidebar } from "./components/OrgSidebar";
|
||||
import { OrgNavBar } from "./components/OrgNavBar";
|
||||
import { RedisBanner } from "./components/RedisBanner";
|
||||
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`}
|
||||
>
|
||||
<Navbar />
|
||||
{!isLoading && !serverDetails?.redisConfigured && <RedisBanner />}
|
||||
{!isLoading && !serverDetails?.emailConfigured && <SmtpBanner />}
|
||||
{!isLoading && subscription.auditLogs && <AuditLogBanner />}
|
||||
{!window.isSecureContext && <InsecureConnectionBanner />}
|
||||
<div className="flex grow flex-col overflow-y-hidden md:flex-row">
|
||||
<OrgSidebar isHidden={isInsideProject} />
|
||||
<div className="flex grow flex-col overflow-y-hidden">
|
||||
<OrgNavBar isHidden={isInsideProject} />
|
||||
{!isLoading && !isInsideProject && !serverDetails?.redisConfigured && <RedisBanner />}
|
||||
{!isLoading && !isInsideProject && !serverDetails?.emailConfigured && <SmtpBanner />}
|
||||
{!isLoading && !isInsideProject && subscription.auditLogs && <AuditLogBanner />}
|
||||
{!window.isSecureContext && !isInsideProject && <InsecureConnectionBanner />}
|
||||
<main
|
||||
className={twMerge(
|
||||
"flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-4 pt-8 pb-4 dark:scheme-dark",
|
||||
isInsideProject && "p-0"
|
||||
"flex-1 overflow-x-hidden bg-bunker-800 px-12 pt-10 pb-4 dark:scheme-dark",
|
||||
isInsideProject ? "overflow-y-hidden p-0" : "overflow-y-auto"
|
||||
)}
|
||||
>
|
||||
<Outlet />
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
faEnvelope,
|
||||
faExclamationTriangle,
|
||||
faGlobe,
|
||||
faInfinity,
|
||||
faInfo,
|
||||
faInfoCircle,
|
||||
faServer,
|
||||
@@ -43,7 +44,7 @@ import { envConfig } from "@app/config/env";
|
||||
import { useOrganization, useSubscription, useUser } from "@app/context";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
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 { MfaMethod } from "@app/hooks/api/auth/types";
|
||||
import { getAuthToken } from "@app/hooks/api/reactQuery";
|
||||
@@ -162,6 +163,8 @@ export const Navbar = () => {
|
||||
await navigateUserToOrg(navigate, orgId);
|
||||
};
|
||||
|
||||
const { mutateAsync } = useGetOrgTrialUrl();
|
||||
|
||||
const logout = useLogoutUser();
|
||||
const logOutUser = async () => {
|
||||
try {
|
||||
@@ -201,144 +204,192 @@ export const Navbar = () => {
|
||||
|
||||
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 (
|
||||
<div className="z-10 flex min-h-12 items-center border-b border-mineshaft-600 bg-mineshaft-800 px-4">
|
||||
<div>
|
||||
<Link to="/organization/projects">
|
||||
<img alt="infisical logo" src="/images/logotransparent.png" className="h-4" />
|
||||
</Link>
|
||||
</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>
|
||||
<div className="z-10 flex min-h-12 items-center bg-mineshaft-900 px-4 pt-1">
|
||||
<div className="mr-auto flex items-center overflow-hidden">
|
||||
<div className="shrink-0">
|
||||
<Link to="/organization/projects">
|
||||
<img alt="infisical logo" src="/images/logotransparent.png" className="h-4" />
|
||||
</Link>
|
||||
<p className="pr-3 pl-3 text-lg text-mineshaft-400/70">/</p>
|
||||
{breadcrumbs ? (
|
||||
// scott: remove /admin as we show server console above
|
||||
<BreadcrumbContainer breadcrumbs={breadcrumbs.slice(1) as TBreadcrumbFormat[]} />
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
<DropdownMenu modal={false}>
|
||||
<Link to="/organization/projects">
|
||||
<div className="group flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary">
|
||||
<Badge
|
||||
variant="org"
|
||||
className={twMerge("text-sm", !isOrgScope && "bg-transparent opacity-75")}
|
||||
>
|
||||
<FontAwesomeIcon icon={faGlobe} />
|
||||
{currentOrg?.name}
|
||||
</Badge>
|
||||
<div className="mr-1 rounded-sm border border-mineshaft-500 px-1 text-xs text-bunker-300 no-underline!">
|
||||
{getPlan(subscription)}
|
||||
</div>
|
||||
{subscription.cardDeclined && (
|
||||
<Tooltip
|
||||
content={`Your payment could not be processed${subscription.cardDeclinedReason ? `: ${subscription.cardDeclinedReason}` : ""}. Please update your payment method to continue enjoying premium features.`}
|
||||
className="max-w-xs"
|
||||
</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>
|
||||
<p className="pr-3 pl-3 text-lg text-mineshaft-400/70">/</p>
|
||||
{breadcrumbs ? (
|
||||
// scott: remove /admin as we show server console above
|
||||
<BreadcrumbContainer breadcrumbs={breadcrumbs.slice(1) as TBreadcrumbFormat[]} />
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center overflow-hidden">
|
||||
<DropdownMenu modal={false}>
|
||||
<Link className="overflow-hidden" to="/organization/projects">
|
||||
<div className="group flex cursor-pointer items-center gap-2 overflow-hidden text-sm text-white transition-all duration-100 hover:text-primary">
|
||||
<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={faExclamationTriangle}
|
||||
className="animate-pulse cursor-help text-xs text-primary-400"
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
<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" />
|
||||
)
|
||||
}
|
||||
<FontAwesomeIcon icon={faGlobe} />
|
||||
<p className="truncate">{currentOrg?.name}</p>
|
||||
</Badge>
|
||||
<div className="mr-1 rounded-sm border border-mineshaft-500 px-1 text-xs text-bunker-300 no-underline!">
|
||||
{getPlan(subscription)}
|
||||
</div>
|
||||
{subscription.cardDeclined && (
|
||||
<Tooltip
|
||||
content={`Your payment could not be processed${subscription.cardDeclinedReason ? `: ${subscription.cardDeclinedReason}` : ""}. Please update your payment method to continue enjoying premium features.`}
|
||||
className="max-w-xs"
|
||||
>
|
||||
<div className="flex w-full max-w-[150px] items-center justify-between truncate">
|
||||
{org.name}
|
||||
<div className="flex items-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationTriangle}
|
||||
className="animate-pulse cursor-help text-xs text-primary-400"
|
||||
/>
|
||||
</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>
|
||||
<p className="pr-3 pl-1 text-lg text-mineshaft-400/70">/</p>
|
||||
{breadcrumbs ? (
|
||||
<BreadcrumbContainer breadcrumbs={breadcrumbs as TBreadcrumbFormat[]} />
|
||||
) : null}
|
||||
</>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
<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">
|
||||
{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}>
|
||||
<DropdownMenuTrigger>
|
||||
<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";
|
||||
@@ -1,19 +1,9 @@
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
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 { Link, Outlet, useLocation } from "@tanstack/react-router";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
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 { usePopUp } from "@app/hooks";
|
||||
|
||||
@@ -23,7 +13,7 @@ export const PamLayout = () => {
|
||||
const { currentProject } = useProject();
|
||||
const { subscription } = useSubscription();
|
||||
const { assumedPrivilegeDetails } = useProjectPermission();
|
||||
|
||||
const location = useLocation();
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -35,152 +25,85 @@ export const PamLayout = () => {
|
||||
return (
|
||||
<>
|
||||
<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
|
||||
key="menu-project-items"
|
||||
initial={{ x: -150 }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: -150 }}
|
||||
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">
|
||||
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
<Lottie className="inline-block h-5 w-5 shrink-0" icon="groups" />
|
||||
PAM
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Menu>
|
||||
<MenuGroup title="Resources">
|
||||
<Link
|
||||
to="/projects/pam/$projectId/accounts"
|
||||
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>
|
||||
<nav className="w-full">
|
||||
<Tabs value="selected">
|
||||
<TabList className="border-b-0">
|
||||
<Link
|
||||
to="/projects/pam/$projectId/accounts"
|
||||
params={{
|
||||
projectId: currentProject.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Accounts</Tab>}
|
||||
</Link>
|
||||
</Menu>
|
||||
</div>
|
||||
<Link
|
||||
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>
|
||||
</motion.div>
|
||||
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 p-4 pt-8">
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
<UpgradePlanModal
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
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 { Link, Outlet } from "@tanstack/react-router";
|
||||
|
||||
import { WishForm } from "@app/components/features/WishForm";
|
||||
import { Outlet } from "@tanstack/react-router";
|
||||
|
||||
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
|
||||
|
||||
@@ -12,27 +10,10 @@ export const PersonalSettingsLayout = () => {
|
||||
|
||||
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 />}
|
||||
<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">
|
||||
<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">
|
||||
<main className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4 dark:scheme-dark">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,10 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
faBell,
|
||||
faBook,
|
||||
faCertificate,
|
||||
faCog,
|
||||
faFileLines,
|
||||
faHome,
|
||||
faMobile,
|
||||
faPlug,
|
||||
faPuzzlePiece,
|
||||
faSitemap,
|
||||
faStamp,
|
||||
faUsers
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faMobile } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, Outlet } from "@tanstack/react-router";
|
||||
import { Link, Outlet, useLocation } from "@tanstack/react-router";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
||||
import { Tab, TabList, Tabs } from "@app/components/v2";
|
||||
import { useProject, useProjectPermission } from "@app/context";
|
||||
|
||||
import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePrivilegeModeBanner";
|
||||
@@ -27,223 +14,139 @@ export const PkiManagerLayout = () => {
|
||||
const { assumedPrivilegeDetails } = useProjectPermission();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const location = useLocation();
|
||||
return (
|
||||
<>
|
||||
<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
|
||||
key="menu-project-items"
|
||||
initial={{ x: -150 }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: -150 }}
|
||||
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">
|
||||
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
<Lottie className="inline-block h-5 w-5 shrink-0" icon="note" />
|
||||
PKI Manager
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Menu>
|
||||
<MenuGroup title="Resources">
|
||||
<Link
|
||||
to="/projects/cert-management/$projectId/subscribers"
|
||||
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>
|
||||
<nav className="w-full">
|
||||
<Tabs value="selected">
|
||||
<TabList className="border-b-0">
|
||||
<Link
|
||||
to="/projects/cert-management/$projectId/subscribers"
|
||||
params={{
|
||||
projectId: currentProject.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Subscribers</Tab>}
|
||||
</Link>
|
||||
</Menu>
|
||||
</div>
|
||||
<Link
|
||||
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>
|
||||
</motion.div>
|
||||
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 p-4 pt-8">
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
<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;
|
||||
|
||||
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>
|
||||
<FontAwesomeIcon icon={faInfoCircle} className="mr-2" />
|
||||
You are currently viewing the project with privileges of{" "}
|
||||
@@ -24,7 +24,7 @@ export const AssumePrivilegeModeBanner = () => {
|
||||
{assumedPrivilegeDetails?.actorName}
|
||||
</b>
|
||||
</div>
|
||||
<div>
|
||||
<div className="ml-auto">
|
||||
<Button
|
||||
size="xs"
|
||||
variant="outline_bg"
|
||||
|
||||
@@ -35,10 +35,19 @@ import {
|
||||
import { getProjectHomePage } from "@app/helpers/project";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
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 { 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 = () => {
|
||||
const [searchProject, setSearchProject] = useState("");
|
||||
const { currentProject: currentWorkspace } = useProject();
|
||||
@@ -99,26 +108,24 @@ export const ProjectSelect = () => {
|
||||
}, [projects, projectFavorites, currentWorkspace]);
|
||||
|
||||
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}>
|
||||
<Link
|
||||
to={getProjectHomePage(currentWorkspace.type, currentWorkspace.environments)}
|
||||
params={{
|
||||
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">
|
||||
<Tooltip content={currentWorkspace.name} className="max-w-96 break-words">
|
||||
<Badge
|
||||
variant="project"
|
||||
className="max-w-44 overflow-hidden text-sm text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
<FontAwesomeIcon icon={faCube} />
|
||||
|
||||
{currentWorkspace?.name}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<p className="inline-block truncate text-mineshaft-200 group-hover:underline">
|
||||
{currentWorkspace?.name}
|
||||
</p>
|
||||
<Badge variant="project" className="cursor-pointer">
|
||||
<FontAwesomeIcon icon={faCube} />
|
||||
<span>
|
||||
{currentWorkspace.type ? PROJECT_TYPE_NAME[currentWorkspace.type] : "Project"}
|
||||
</span>
|
||||
</Badge>
|
||||
</Link>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div>
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
faArrowsSpin,
|
||||
faBook,
|
||||
faCheckToSlot,
|
||||
faCog,
|
||||
faHome,
|
||||
faMobile,
|
||||
faPlug,
|
||||
faPuzzlePiece,
|
||||
faUsers,
|
||||
faVault
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faMobile } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, Outlet, useLocation } from "@tanstack/react-router";
|
||||
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 {
|
||||
useGetAccessRequestsCount,
|
||||
@@ -28,10 +17,10 @@ import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePri
|
||||
export const SecretManagerLayout = () => {
|
||||
const { currentProject, projectId } = useProject();
|
||||
const { assumedPrivilegeDetails } = useProjectPermission();
|
||||
const location = useLocation();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const projectSlug = currentProject?.slug || "";
|
||||
const location = useLocation();
|
||||
|
||||
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({
|
||||
projectId
|
||||
@@ -54,208 +43,131 @@ export const SecretManagerLayout = () => {
|
||||
return (
|
||||
<>
|
||||
<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
|
||||
key="menu-project-items"
|
||||
initial={{ x: -150 }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: -150 }}
|
||||
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">
|
||||
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
<Lottie className="inline-block h-5 w-5 shrink-0" icon="vault" />
|
||||
Secrets Manager
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Menu>
|
||||
<MenuGroup title="Resources">
|
||||
<Link
|
||||
to="/projects/secret-management/$projectId/overview"
|
||||
params={{
|
||||
projectId: currentProject.id,
|
||||
...(currentProject.environments.length
|
||||
? { envSlug: currentProject.environments[0]?.slug }
|
||||
: {})
|
||||
}}
|
||||
>
|
||||
{({ 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
|
||||
}}
|
||||
<nav className="w-full">
|
||||
<Tabs value="selected">
|
||||
<TabList className="border-b-0">
|
||||
<Link
|
||||
to="/projects/secret-management/$projectId/overview"
|
||||
params={{
|
||||
projectId: currentProject.id,
|
||||
...(currentProject.environments.length
|
||||
? { envSlug: currentProject.environments[0]?.slug }
|
||||
: {})
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<Tab
|
||||
value={
|
||||
isActive || location.pathname.match(/\/secrets\/|\/commits\//)
|
||||
? "selected"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<div className="w-6">
|
||||
<FontAwesomeIcon icon={faArrowsSpin} />
|
||||
</div>
|
||||
Secret Rotations
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
Overview
|
||||
</Tab>
|
||||
)}
|
||||
<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>
|
||||
</Menu>
|
||||
</div>
|
||||
<Link
|
||||
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>
|
||||
</motion.div>
|
||||
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 p-4 pt-8">
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
<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 {
|
||||
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 { Link, Outlet, useLocation } from "@tanstack/react-router";
|
||||
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 {
|
||||
ProjectPermissionSub,
|
||||
useProject,
|
||||
@@ -29,6 +19,7 @@ export const SecretScanningLayout = () => {
|
||||
|
||||
const { permission } = useProjectPermission();
|
||||
const { subscription } = useSubscription();
|
||||
const location = useLocation();
|
||||
|
||||
const { data: unresolvedFindings } = useGetSecretScanningUnresolvedFindingCount(
|
||||
currentProject.id,
|
||||
@@ -45,158 +36,94 @@ export const SecretScanningLayout = () => {
|
||||
|
||||
return (
|
||||
<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
|
||||
key="menu-project-items"
|
||||
initial={{ x: -150 }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: -150 }}
|
||||
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">
|
||||
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
<Lottie className="inline-block h-5 w-5 shrink-0" icon="secret-scan" />
|
||||
Secret Scanning
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Menu>
|
||||
<MenuGroup title="Resources">
|
||||
<Link
|
||||
to="/projects/secret-scanning/$projectId/data-sources"
|
||||
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>
|
||||
<nav className="w-full">
|
||||
<Tabs value="selected">
|
||||
<TabList className="border-b-0">
|
||||
<Link
|
||||
to="/projects/secret-scanning/$projectId/data-sources"
|
||||
params={{
|
||||
projectId: currentProject.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => <Tab value={isActive ? "selected" : ""}>Data Sources</Tab>}
|
||||
</Link>
|
||||
</Menu>
|
||||
</div>
|
||||
<Link
|
||||
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>
|
||||
</motion.div>
|
||||
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 p-4 pt-8">
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
import {
|
||||
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 { Link, Outlet, useLocation } from "@tanstack/react-router";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { Lottie, Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
||||
import { Tab, TabList, Tabs } from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
@@ -24,149 +15,104 @@ import { AssumePrivilegeModeBanner } from "../ProjectLayout/components/AssumePri
|
||||
export const SshLayout = () => {
|
||||
const { currentProject } = useProject();
|
||||
const { assumedPrivilegeDetails } = useProjectPermission();
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<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
|
||||
key="menu-project-items"
|
||||
initial={{ x: -150 }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: -150 }}
|
||||
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">
|
||||
<div className="flex items-center gap-3 border-b border-mineshaft-600 px-4 py-3.5 text-lg text-white">
|
||||
<Lottie className="inline-block h-5 w-5 shrink-0" icon="terminal" />
|
||||
SSH
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<Menu>
|
||||
<MenuGroup title="Resources">
|
||||
<Link
|
||||
to="/projects/ssh/$projectId/overview"
|
||||
params={{
|
||||
projectId: currentProject.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<MenuItem variant="project" isSelected={isActive}>
|
||||
<div className="mx-1 flex gap-2">
|
||||
<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>
|
||||
<nav className="w-full">
|
||||
<Tabs value="selected">
|
||||
<TabList className="border-b-0">
|
||||
<Link
|
||||
to="/projects/ssh/$projectId/overview"
|
||||
params={{
|
||||
projectId: currentProject.id
|
||||
}}
|
||||
>
|
||||
{({ isActive }) => (
|
||||
<Tab
|
||||
value={
|
||||
isActive || location.pathname.match(/\/ssh-host-groups\//) ? "selected" : ""
|
||||
}
|
||||
>
|
||||
Hosts
|
||||
</Tab>
|
||||
)}
|
||||
</Link>
|
||||
</Menu>
|
||||
</div>
|
||||
<ProjectPermissionCan
|
||||
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>
|
||||
</motion.div>
|
||||
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 p-4 pt-8">
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
{assumedPrivilegeDetails && <AssumePrivilegeModeBanner />}
|
||||
<div className="flex-1 overflow-x-hidden overflow-y-auto bg-bunker-800 px-12 pt-10 pb-4">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -13,8 +13,8 @@ export const AccessManagementPage = () => {
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: "Access Control" })}</title>
|
||||
</Helmet>
|
||||
<div className="container 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 flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="Access Control"
|
||||
|
||||
@@ -13,8 +13,8 @@ export const AuthenticationPage = () => {
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: "Admin" })}</title>
|
||||
</Helmet>
|
||||
<div className="container 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 flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="Authentication"
|
||||
|
||||
@@ -13,8 +13,8 @@ export const CachingPage = () => {
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: "Admin" })}</title>
|
||||
</Helmet>
|
||||
<div className="container 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 flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="Caching"
|
||||
|
||||
@@ -13,8 +13,8 @@ export const EncryptionPage = () => {
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: "Admin" })}</title>
|
||||
</Helmet>
|
||||
<div className="container 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 flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="Encryption"
|
||||
|
||||
@@ -13,8 +13,8 @@ export const EnvironmentPage = () => {
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: "Admin" })}</title>
|
||||
</Helmet>
|
||||
<div className="container 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 flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="Environment Variables"
|
||||
|
||||
@@ -15,8 +15,8 @@ export const GeneralPage = () => {
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: "Admin" })}</title>
|
||||
</Helmet>
|
||||
<div className="container 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 flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="General"
|
||||
|
||||
@@ -13,8 +13,8 @@ export const IntegrationsPage = () => {
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: "Admin" })}</title>
|
||||
</Helmet>
|
||||
<div className="container 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 flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="Integrations"
|
||||
|
||||
@@ -13,18 +13,24 @@ export const ResourceOverviewPage = () => {
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: "Resource Overview" })}</title>
|
||||
</Helmet>
|
||||
<div className="container 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 flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="instance"
|
||||
title="Resource Overview"
|
||||
description="Manage resources within your Infisical instance."
|
||||
/>
|
||||
<Tabs defaultValue="tab-organizations">
|
||||
<Tabs orientation="vertical" defaultValue="tab-organizations">
|
||||
<TabList>
|
||||
<Tab value="tab-organizations">Organizations</Tab>
|
||||
<Tab value="tab-users">Users</Tab>
|
||||
<Tab value="tab-identities">Identities</Tab>
|
||||
<Tab variant="instance" value="tab-organizations">
|
||||
Organizations
|
||||
</Tab>
|
||||
<Tab variant="instance" value="tab-users">
|
||||
Users
|
||||
</Tab>
|
||||
<Tab variant="instance" value="tab-identities">
|
||||
Identities
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanel value="tab-organizations">
|
||||
<OrganizationsTable />
|
||||
|
||||
@@ -4,19 +4,20 @@ import { useTranslation } from "react-i18next";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { PkiAlertsSection } from "./components";
|
||||
|
||||
export const AlertingPage = () => {
|
||||
const { t } = useTranslation();
|
||||
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>
|
||||
<title>{t("common.head-title", { title: "Alerting" })}</title>
|
||||
</Helmet>
|
||||
<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"
|
||||
scope={ProjectType.CertificateManager}
|
||||
title="Alerting"
|
||||
description="Configure alerts for expiring certificates and CAs to maintain security and compliance."
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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 { createNotification } from "@app/components/notifications";
|
||||
@@ -18,6 +20,7 @@ import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
|
||||
import { CaType, useDeleteCa, useGetCa } from "@app/hooks/api";
|
||||
import { TInternalCertificateAuthority } from "@app/hooks/api/ca/types";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { CaInstallCertModal } from "../CertificateAuthoritiesPage/components/CaInstallCertModal";
|
||||
@@ -84,10 +87,24 @@ const Page = () => {
|
||||
};
|
||||
|
||||
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 && (
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader scope="project" title={data.name}>
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<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>
|
||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||
<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 { PageHeader } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { ExternalCaSection } from "./components/ExternalCaSection";
|
||||
import { CaSection } from "./components";
|
||||
@@ -11,13 +12,13 @@ import { CaSection } from "./components";
|
||||
export const CertificateAuthoritiesPage = () => {
|
||||
const { t } = useTranslation();
|
||||
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>
|
||||
<title>{t("common.head-title", { title: "Certificate Authorities" })}</title>
|
||||
</Helmet>
|
||||
<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"
|
||||
scope={ProjectType.CertificateManager}
|
||||
title="Certificate Authorities"
|
||||
description="Manage certificate authorities for issuing and signing certificates"
|
||||
/>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ProjectPermissionSub,
|
||||
useProjectPermission
|
||||
} from "@app/context";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { PkiCollectionSection } from "../AlertingPage/components";
|
||||
import { CertificatesSection } from "./components";
|
||||
@@ -27,13 +28,13 @@ export const CertificatesPage = () => {
|
||||
);
|
||||
|
||||
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>
|
||||
<title>{t("common.head-title", { title: "Certificates" })}</title>
|
||||
</Helmet>
|
||||
<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"
|
||||
scope={ProjectType.CertificateManager}
|
||||
title="Certificates"
|
||||
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 { ProjectPermissionSub, useProject } from "@app/context";
|
||||
import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import { PkiSyncsTab } from "./components";
|
||||
@@ -42,16 +43,18 @@ export const IntegrationsListPage = () => {
|
||||
<meta property="og:title" content="Manage your certificates in seconds" />
|
||||
<meta name="og:description" content="Sync and manage PKI certificates across services" />
|
||||
</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">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
scope={ProjectType.CertificateManager}
|
||||
title="Integrations"
|
||||
description="Manage integrations with third-party certificate services."
|
||||
/>
|
||||
<Tabs value={currentTab} onValueChange={updateSelectedTab}>
|
||||
<Tabs orientation="vertical" value={currentTab} onValueChange={updateSelectedTab}>
|
||||
<TabList>
|
||||
<Tab value={IntegrationsListPageTabs.PkiSyncs}>Certificate Syncs</Tab>
|
||||
<Tab variant="project" value={IntegrationsListPageTabs.PkiSyncs}>
|
||||
Certificate Syncs
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanel value={IntegrationsListPageTabs.PkiSyncs}>
|
||||
<ProjectPermissionCan
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
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 { createNotification } from "@app/components/notifications";
|
||||
@@ -19,6 +21,7 @@ import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
|
||||
import { useDeletePkiCollection, useGetPkiCollectionById } from "@app/hooks/api";
|
||||
import { PkiItemType } from "@app/hooks/api/pkiCollections/constants";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { PkiCollectionModal } from "../AlertingPage/components/PkiCollectionModal";
|
||||
@@ -70,10 +73,24 @@ export const PkiCollectionPage = () => {
|
||||
};
|
||||
|
||||
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 && (
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader scope="project" title={data.name}>
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<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>
|
||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
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 { createNotification } from "@app/components/notifications";
|
||||
@@ -22,6 +24,7 @@ import {
|
||||
useProject
|
||||
} from "@app/context";
|
||||
import { useDeletePkiSubscriber, useGetPkiSubscriber } from "@app/hooks/api";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { PkiSubscriberModal } from "../PkiSubscribersPage/components/PkiSubscriberModal";
|
||||
@@ -75,10 +78,24 @@ const Page = () => {
|
||||
};
|
||||
|
||||
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 && (
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader scope="project" title={data.name}>
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<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>
|
||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||
<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 { PageHeader } from "@app/components/v2";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { PkiSubscriberSection } from "./components";
|
||||
|
||||
@@ -13,10 +14,10 @@ export const PkiSubscribersPage = () => {
|
||||
<title>{t("common.head-title", { title: "PKI Subscribers" })}</title>
|
||||
</Helmet>
|
||||
<div className="h-full bg-bunker-800">
|
||||
<div className="container mx-auto flex flex-col justify-between text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<div className="mx-auto flex flex-col justify-between text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
scope={ProjectType.CertificateManager}
|
||||
title="Subscribers"
|
||||
description="Manage subscribers that request and receive certificates, including user devices, servers, and services."
|
||||
/>
|
||||
|
||||
@@ -68,8 +68,8 @@ const PageContent = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="container 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 flex flex-col justify-between bg-bunker-800 font-inter text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<Button
|
||||
variant="link"
|
||||
type="submit"
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteCertTemplateV2 } from "@app/hooks/api";
|
||||
import { useListCertificateTemplates } from "@app/hooks/api/certificateTemplates/queries";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { CertificateTemplateEnrollmentModal } from "../CertificatesPage/components/CertificateTemplateEnrollmentModal";
|
||||
import { PkiTemplateForm } from "./components/PkiTemplateForm";
|
||||
@@ -103,15 +104,15 @@ export const PkiTemplateListPage = () => {
|
||||
<title>{t("common.head-title", { title: "PKI Templates" })}</title>
|
||||
</Helmet>
|
||||
<div className="h-full bg-bunker-800">
|
||||
<div className="container mx-auto flex flex-col justify-between text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<div className="mx-auto flex flex-col justify-between text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
scope={ProjectType.CertificateManager}
|
||||
title="Certificate Templates"
|
||||
description="Manage certificate template to request and issue dynamic certificates following a strict format."
|
||||
/>
|
||||
</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">
|
||||
<p className="text-xl font-medium text-mineshaft-100">Templates</p>
|
||||
<div className="flex w-full justify-end">
|
||||
@@ -267,7 +268,7 @@ export const PkiTemplateListPage = () => {
|
||||
onDeleteApproved={() => onRemovePkiSubscriberSubmit()}
|
||||
/>
|
||||
</div>
|
||||
<div className="container mx-auto max-w-7xl" />
|
||||
<div className="container mx-auto max-w-8xl" />
|
||||
</div>
|
||||
<Modal
|
||||
isOpen={popUp?.certificateTemplate?.isOpen}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
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";
|
||||
|
||||
const tabs = [
|
||||
@@ -20,12 +21,12 @@ export const SettingsPage = () => {
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||
</Helmet>
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader scope="project" title={t("settings.project.title")} />
|
||||
<Tabs defaultValue={tabs[0].key}>
|
||||
<div className="w-full max-w-8xl">
|
||||
<PageHeader scope={ProjectType.CertificateManager} title={t("settings.project.title")} />
|
||||
<Tabs orientation="vertical" defaultValue={tabs[0].key}>
|
||||
<TabList>
|
||||
{tabs.map((tab) => (
|
||||
<Tab value={tab.key} key={tab.key}>
|
||||
<Tab value={tab.key} variant="project" key={tab.key}>
|
||||
{tab.name}
|
||||
</Tab>
|
||||
))}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
import { ProjectPermissionKmipActions, ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { KmipClientTable } from "./components/KmipClientTable";
|
||||
|
||||
@@ -15,10 +16,10 @@ export const KmipPage = () => {
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: "KMS" })}</title>
|
||||
</Helmet>
|
||||
<div className="container 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 flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
scope={ProjectType.KMS}
|
||||
title="KMIP"
|
||||
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 { PageHeader } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { CmekTable } from "./components";
|
||||
|
||||
@@ -15,10 +16,10 @@ export const OverviewPage = () => {
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: "KMS" })}</title>
|
||||
</Helmet>
|
||||
<div className="container 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 flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
scope={ProjectType.KMS}
|
||||
title="Overview"
|
||||
description="Manage keys and perform cryptographic operations."
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
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";
|
||||
|
||||
const tabs = [
|
||||
@@ -20,12 +21,16 @@ export const SettingsPage = () => {
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||
</Helmet>
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader scope="project" title="Settings" />
|
||||
<Tabs defaultValue={tabs[0].key}>
|
||||
<div className="w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope={ProjectType.KMS}
|
||||
title="Settings"
|
||||
description="Configure general project settings"
|
||||
/>
|
||||
<Tabs orientation="vertical" defaultValue={tabs[0].key}>
|
||||
<TabList>
|
||||
{tabs.map((tab) => (
|
||||
<Tab value={tab.key} key={tab.key}>
|
||||
<Tab variant="project" value={tab.key} key={tab.key}>
|
||||
{tab.name}
|
||||
</Tab>
|
||||
))}
|
||||
|
||||
@@ -76,11 +76,11 @@ export const AccessManagementPage = () => {
|
||||
const hasNoAccess = tabSections.every((tab) => tab.isHidden);
|
||||
|
||||
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>
|
||||
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
|
||||
</Helmet>
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="org"
|
||||
title="Access Control"
|
||||
@@ -111,7 +111,7 @@ export const AccessManagementPage = () => {
|
||||
isOpen={isUpgradePrivilegeSystemModalOpen}
|
||||
onOpenChange={setIsUpgradePrivilegeSystemModalOpen}
|
||||
/>
|
||||
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
|
||||
<Tabs orientation="vertical" value={selectedTab} onValueChange={updateSelectedTab}>
|
||||
<TabList>
|
||||
{tabSections
|
||||
.filter((el) => !el.isHidden)
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { OrgGroupsSection } from "./components";
|
||||
|
||||
export const OrgGroupsTab = () => {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
return <OrgGroupsSection />;
|
||||
};
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { IdentitySection } from "./components";
|
||||
|
||||
export const OrgIdentityTab = () => {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
return <IdentitySection />;
|
||||
};
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { OrgMembersSection } from "./components";
|
||||
|
||||
export const OrgMembersTab = () => {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
return <OrgMembersSection />;
|
||||
};
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { OrgRoleTable } from "./OrgRoleTable";
|
||||
|
||||
export const OrgRoleTabSection = () => {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
return <OrgRoleTable />;
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ export const AppConnectionsPage = withPermission(
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Helmet>
|
||||
<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"
|
||||
className="w-full"
|
||||
|
||||
@@ -12,9 +12,8 @@ export const AuditLogsPage = () => {
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Helmet>
|
||||
|
||||
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<div className="flex w-full justify-center bg-bunker-800 pb-6 text-white">
|
||||
<div className="w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="org"
|
||||
title="Audit Logs"
|
||||
|
||||
@@ -18,7 +18,7 @@ export const BillingPage = () => {
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Helmet>
|
||||
<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
|
||||
scope="org"
|
||||
title={t("billing.title")}
|
||||
|
||||
@@ -17,15 +17,15 @@ const tabs = [
|
||||
|
||||
export const BillingTabGroup = withPermission(
|
||||
() => {
|
||||
const tabsFiltered = isInfisicalCloud()
|
||||
? tabs
|
||||
: [{ name: "Infisical Self-Hosted", key: "tab-infisical-cloud" }];
|
||||
if (!isInfisicalCloud()) {
|
||||
return <BillingCloudTab />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs defaultValue={tabs[0].key}>
|
||||
<Tabs orientation="vertical" defaultValue={tabs[0].key}>
|
||||
<TabList>
|
||||
{tabsFiltered.map((tab) => (
|
||||
<Tab variant="org" value={tab.key}>
|
||||
{tabs.map((tab) => (
|
||||
<Tab variant="org" key={tab.key} value={tab.key}>
|
||||
{tab.name}
|
||||
</Tab>
|
||||
))}
|
||||
@@ -33,19 +33,15 @@ export const BillingTabGroup = withPermission(
|
||||
<TabPanel value={tabs[0].key}>
|
||||
<BillingCloudTab />
|
||||
</TabPanel>
|
||||
{isInfisicalCloud() && (
|
||||
<>
|
||||
<TabPanel value={tabs[1].key}>
|
||||
<BillingSelfHostedTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={tabs[2].key}>
|
||||
<BillingReceiptsTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={tabs[3].key}>
|
||||
<BillingDetailsTab />
|
||||
</TabPanel>
|
||||
</>
|
||||
)}
|
||||
<TabPanel value={tabs[1].key}>
|
||||
<BillingSelfHostedTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={tabs[2].key}>
|
||||
<BillingReceiptsTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={tabs[3].key}>
|
||||
<BillingDetailsTab />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
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 { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
@@ -80,10 +82,20 @@ const Page = () => {
|
||||
if (isPending) return <Spinner size="sm" className="mt-2 ml-2" />;
|
||||
|
||||
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 && (
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader scope="org" title={data.group.name}>
|
||||
<div className="mx-auto w-full max-w-8xl">
|
||||
<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>
|
||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
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 { createNotification } from "@app/components/notifications";
|
||||
@@ -72,10 +74,20 @@ const Page = () => {
|
||||
};
|
||||
|
||||
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 && (
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader scope="org" title={data.identity.name} />
|
||||
<div className="mx-auto w-full max-w-8xl">
|
||||
<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="mr-4 w-96">
|
||||
<IdentityDetailsSection identityId={identityId} handlePopUpOpen={handlePopUpOpen} />
|
||||
|
||||
@@ -12,7 +12,7 @@ export const NetworkingPage = () => {
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Helmet>
|
||||
<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="Networking"
|
||||
|
||||
@@ -19,7 +19,7 @@ export const NetworkingTabGroup = () => {
|
||||
const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key);
|
||||
|
||||
return (
|
||||
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
|
||||
<Tabs orientation="vertical" value={selectedTab} onValueChange={setSelectedTab}>
|
||||
<TabList>
|
||||
{tabs.map((tab) => (
|
||||
<Tab variant="org" value={tab.key} key={tab.key}>
|
||||
|
||||
@@ -58,18 +58,16 @@ export const ProjectsPage = () => {
|
||||
: true;
|
||||
|
||||
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>
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Helmet>
|
||||
<div className="mb-4 flex flex-col items-start justify-start">
|
||||
<PageHeader
|
||||
scope="org"
|
||||
title="Overview"
|
||||
description="Your team's complete security toolkit - organized and ready when you need them."
|
||||
/>
|
||||
</div>
|
||||
<PageHeader
|
||||
scope="org"
|
||||
title="Overview"
|
||||
description="Your team's complete security toolkit - organized and ready when you need them."
|
||||
/>
|
||||
{projectListView === ProjectListView.MyProjects ? (
|
||||
<MyProjectView
|
||||
onAddNewProject={() => handlePopUpOpen("addNewWs")}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
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 { useNavigate, useParams } from "@tanstack/react-router";
|
||||
import { Link, useNavigate, useParams } from "@tanstack/react-router";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
@@ -77,20 +77,26 @@ export const Page = () => {
|
||||
const isCustomRole = !["admin", "member", "no-access"].includes(data?.slug ?? "");
|
||||
|
||||
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 && (
|
||||
<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
|
||||
scope="org"
|
||||
title={
|
||||
<div className="flex flex-col">
|
||||
<div>
|
||||
<span>{data.name}</span>
|
||||
<p className="text-sm leading-3 font-normal text-mineshaft-400 normal-case">
|
||||
{data.slug} {data.description && `- ${data.description}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
title={data.name}
|
||||
description={
|
||||
<>
|
||||
{data.slug} {data.description && `- ${data.description}`}
|
||||
</>
|
||||
}
|
||||
>
|
||||
{isCustomRole && (
|
||||
|
||||
@@ -20,7 +20,7 @@ export const SecretSharingPage = () => {
|
||||
<meta name="og:description" content={String(t("approval.og-description"))} />
|
||||
</Helmet>
|
||||
<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
|
||||
scope="org"
|
||||
title="Secret Sharing"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
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 { RequestSecretTab } from "./components/RequestSecret/RequestSecretTab";
|
||||
@@ -34,14 +34,13 @@ export const ShareSecretSection = () => {
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Helmet>
|
||||
|
||||
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
|
||||
<Tabs orientation="vertical" value={selectedTab} onValueChange={updateSelectedTab}>
|
||||
<TabList>
|
||||
<Tab value={SecretSharingPageTabs.ShareSecret}>Share Secrets</Tab>
|
||||
<Tab value={SecretSharingPageTabs.RequestSecret}>
|
||||
<Tab variant="org" value={SecretSharingPageTabs.ShareSecret}>
|
||||
Share Secrets
|
||||
</Tab>
|
||||
<Tab variant="org" value={SecretSharingPageTabs.RequestSecret}>
|
||||
Request Secrets
|
||||
<Badge variant="primary" className="ml-1 cursor-pointer text-xs">
|
||||
New
|
||||
</Badge>
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanel value={SecretSharingPageTabs.ShareSecret}>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const SecretSharingSettingsPage = withPermission(
|
||||
<title>{t("common.head-title", { title: "Secret Share Settings" })}</title>
|
||||
</Helmet>
|
||||
<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" />
|
||||
<SecretSharingSettingsTabGroup />
|
||||
</div>
|
||||
|
||||
@@ -14,8 +14,12 @@ export const SettingsPage = () => {
|
||||
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
|
||||
</Helmet>
|
||||
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader scope="org" title={t("settings.org.title")} />
|
||||
<div className="w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="org"
|
||||
description="Configure organization-wide settings"
|
||||
title={t("settings.org.title")}
|
||||
/>
|
||||
<OrgTabGroup />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -55,7 +55,7 @@ export const OrgTabGroup = () => {
|
||||
const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key);
|
||||
|
||||
return (
|
||||
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
|
||||
<Tabs orientation="vertical" value={selectedTab} onValueChange={setSelectedTab}>
|
||||
<TabList>
|
||||
{tabs.map((tab) => (
|
||||
<Tab variant="org" value={tab.key} key={tab.key}>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
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 { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
@@ -115,16 +117,30 @@ const Page = withPermission(
|
||||
};
|
||||
|
||||
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 && (
|
||||
<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
|
||||
scope="org"
|
||||
title={
|
||||
membership.user.firstName || membership.user.lastName
|
||||
? `${membership.user.firstName} ${membership.user.lastName ?? ""}`.trim()
|
||||
: "-"
|
||||
: (membership.user.username ??
|
||||
membership.user.email ??
|
||||
membership.inviteEmail ??
|
||||
"Unknown User")
|
||||
}
|
||||
description="Organization User Membership"
|
||||
>
|
||||
<div>
|
||||
{userId !== membership.user.id && (
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
import { ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectPermissionPamAccountActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { PamAccountsSection } from "./components/PamAccountsSection";
|
||||
|
||||
@@ -21,10 +22,10 @@ export const PamAccountsPage = () => {
|
||||
a={ProjectPermissionSub.PamAccounts}
|
||||
>
|
||||
<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 mb-6 w-full max-w-7xl">
|
||||
<div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
scope={ProjectType.PAM}
|
||||
title="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 { AccountView, AccountViewToggle } from "./AccountViewToggle";
|
||||
import { FolderBreadCrumbs } from "./FolderBreadCrumbs";
|
||||
import { PamAccessAccountModal } from "./PamAccessAccountModal";
|
||||
import { PamAccountRow } from "./PamAccountRow";
|
||||
import { PamAddAccountModal } from "./PamAddAccountModal";
|
||||
@@ -255,6 +256,7 @@ export const PamAccountsTable = ({ accounts, folders, projectId }: Props) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{accountView === AccountView.Nested && <FolderBreadCrumbs path={accountPath} />}
|
||||
<div className="mt-4 flex gap-2">
|
||||
<ProjectPermissionCan I={ProjectPermissionActions.Read} a={ProjectPermissionSub.PamFolders}>
|
||||
{(isAllowed) =>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
import { ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectPermissionPamAccountActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { PamResourcesSection } from "./components/PamResourcesSection";
|
||||
|
||||
@@ -21,10 +22,10 @@ export const PamResourcesPage = () => {
|
||||
a={ProjectPermissionSub.PamResources}
|
||||
>
|
||||
<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 mb-6 w-full max-w-7xl">
|
||||
<div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
scope={ProjectType.PAM}
|
||||
title="Resources"
|
||||
description="Manage resources such as servers, databases, and more."
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
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 { PageHeader } from "@app/components/v2";
|
||||
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 { useGetPamSessionById } from "@app/hooks/api/pam";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { PamSessionDetailsSection } from "./components/PamSessionDetailsSection";
|
||||
import { PamSessionLogsSection } from "./components/PamSessionLogsSection";
|
||||
@@ -17,13 +20,23 @@ const Page = () => {
|
||||
select: (el) => el.sessionId
|
||||
});
|
||||
const { data: session } = useGetPamSessionById(sessionId);
|
||||
|
||||
const { currentProject } = useProject();
|
||||
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 && (
|
||||
<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
|
||||
scope="project"
|
||||
scope={ProjectType.PAM}
|
||||
title={`${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 { ProjectPermissionSub } from "@app/context";
|
||||
import { ProjectPermissionPamSessionActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { PamSessionSection } from "./components/PamSessionSection";
|
||||
|
||||
@@ -21,10 +22,10 @@ export const PamSessionPage = () => {
|
||||
a={ProjectPermissionSub.PamSessions}
|
||||
>
|
||||
<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 mb-6 w-full max-w-7xl">
|
||||
<div className="mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
scope={ProjectType.PAM}
|
||||
title="Sessions"
|
||||
description="Filter and search through account sessions."
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
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";
|
||||
|
||||
export const SettingsPage = () => {
|
||||
@@ -12,11 +13,17 @@ export const SettingsPage = () => {
|
||||
<Helmet>
|
||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||
</Helmet>
|
||||
<div className="w-full max-w-7xl">
|
||||
<PageHeader scope="project" title="Settings" description="Configure your PAM project." />
|
||||
<Tabs defaultValue="tab-project-general">
|
||||
<div className="w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope={ProjectType.PAM}
|
||||
title="Settings"
|
||||
description="Configure your PAM project."
|
||||
/>
|
||||
<Tabs orientation="vertical" defaultValue="tab-project-general">
|
||||
<TabList>
|
||||
<Tab value="tab-project-general">General</Tab>
|
||||
<Tab variant="project" value="tab-project-general">
|
||||
General
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanel value="tab-project-general">
|
||||
<ProjectGeneralTab />
|
||||
|
||||
@@ -37,26 +37,32 @@ const Page = () => {
|
||||
const isSecretManager = currentProject.type === ProjectType.SecretManager;
|
||||
|
||||
return (
|
||||
<div className="container 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 flex flex-col justify-between bg-bunker-800 text-white">
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
scope={currentProject.type}
|
||||
title="Access Control"
|
||||
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>
|
||||
<Tab value={ProjectAccessControlTabs.Member}>Users</Tab>
|
||||
<Tab value={ProjectAccessControlTabs.Groups}>Groups</Tab>
|
||||
<Tab value={ProjectAccessControlTabs.Identities}>
|
||||
<div className="flex items-center">
|
||||
<p>Machine Identities</p>
|
||||
</div>
|
||||
<Tab variant="project" value={ProjectAccessControlTabs.Member}>
|
||||
Users
|
||||
</Tab>
|
||||
<Tab variant="project" value={ProjectAccessControlTabs.Groups}>
|
||||
Groups
|
||||
</Tab>
|
||||
<Tab variant="project" value={ProjectAccessControlTabs.Identities}>
|
||||
Identities
|
||||
</Tab>
|
||||
{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>
|
||||
<TabPanel value={ProjectAccessControlTabs.Member}>
|
||||
<MembersTab />
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
|
||||
@@ -7,17 +5,7 @@ import { GroupsSection } from "./components";
|
||||
|
||||
export const GroupsTab = withProjectPermission(
|
||||
() => {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
return <GroupsSection />;
|
||||
},
|
||||
{
|
||||
action: ProjectPermissionActions.Read,
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { format } from "date-fns";
|
||||
import { motion } from "framer-motion";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -158,307 +157,295 @@ export const IdentityTab = withProjectPermission(
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key="identity-role-panel"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<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}
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<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"
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen("identity")}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Add Identity
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<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>
|
||||
<Input
|
||||
containerClassName="mb-4"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder="Search identities by name..."
|
||||
/>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr className="h-14">
|
||||
<Th className="w-1/3">
|
||||
<div className="flex items-center">
|
||||
Name
|
||||
<IconButton
|
||||
variant="plain"
|
||||
className={`ml-2 ${
|
||||
orderBy === ProjectIdentityOrderBy.Name ? "" : "opacity-30"
|
||||
}`}
|
||||
ariaLabel="sort"
|
||||
onClick={() => handleSort(ProjectIdentityOrderBy.Name)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={
|
||||
orderDirection === OrderByDirection.DESC &&
|
||||
orderBy === ProjectIdentityOrderBy.Name
|
||||
? faArrowUp
|
||||
: faArrowDown
|
||||
}
|
||||
/>
|
||||
</IconButton>
|
||||
</div>
|
||||
</Th>
|
||||
<Th className="w-1/3">Role</Th>
|
||||
<Th>Added on</Th>
|
||||
<Th className="w-5">{isFetching ? <Spinner size="xs" /> : null}</Th>
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isPending && <TableSkeleton columns={4} innerKey="project-identities" />}
|
||||
{!isPending &&
|
||||
data &&
|
||||
data.identityMemberships.length > 0 &&
|
||||
data.identityMemberships.map((identityMember) => {
|
||||
const {
|
||||
identity: { id, name },
|
||||
roles,
|
||||
createdAt
|
||||
} = identityMember;
|
||||
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({
|
||||
to: `${getProjectBaseURL(currentProject.type)}/identities/$identityId` as const,
|
||||
params: {
|
||||
projectId: currentProject.id,
|
||||
identityId: id
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
onClick={() =>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={ProjectPermissionSub.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen("identity")}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Add Identity
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<Input
|
||||
containerClassName="mb-4"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder="Search identities by name..."
|
||||
/>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr className="h-14">
|
||||
<Th className="w-1/3">
|
||||
<div className="flex items-center">
|
||||
Name
|
||||
<IconButton
|
||||
variant="plain"
|
||||
className={`ml-2 ${
|
||||
orderBy === ProjectIdentityOrderBy.Name ? "" : "opacity-30"
|
||||
}`}
|
||||
ariaLabel="sort"
|
||||
onClick={() => handleSort(ProjectIdentityOrderBy.Name)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={
|
||||
orderDirection === OrderByDirection.DESC &&
|
||||
orderBy === ProjectIdentityOrderBy.Name
|
||||
? faArrowUp
|
||||
: faArrowDown
|
||||
}
|
||||
/>
|
||||
</IconButton>
|
||||
</div>
|
||||
</Th>
|
||||
<Th className="w-1/3">Role</Th>
|
||||
<Th>Added on</Th>
|
||||
<Th className="w-5">{isFetching ? <Spinner size="xs" /> : null}</Th>
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isPending && <TableSkeleton columns={4} innerKey="project-identities" />}
|
||||
{!isPending &&
|
||||
data &&
|
||||
data.identityMemberships.length > 0 &&
|
||||
data.identityMemberships.map((identityMember) => {
|
||||
const {
|
||||
identity: { id, name },
|
||||
roles,
|
||||
createdAt
|
||||
} = identityMember;
|
||||
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({
|
||||
to: `${getProjectBaseURL(currentProject.type)}/identities/$identityId` as const,
|
||||
params: {
|
||||
projectId: currentProject.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>
|
||||
<div className="flex items-center space-x-2">
|
||||
{roles
|
||||
.slice(0, MAX_ROLES_TO_BE_SHOWN_IN_TABLE)
|
||||
.map(
|
||||
({
|
||||
role,
|
||||
customRoleName,
|
||||
id: roleId,
|
||||
isTemporary,
|
||||
temporaryAccessEndTime
|
||||
}) => {
|
||||
const isExpired =
|
||||
new Date() > new Date(temporaryAccessEndTime || ("" as string));
|
||||
return (
|
||||
<Tag key={roleId}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="capitalize">
|
||||
{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>
|
||||
)}
|
||||
<Td>
|
||||
<div className="flex items-center space-x-2">
|
||||
{roles
|
||||
.slice(0, MAX_ROLES_TO_BE_SHOWN_IN_TABLE)
|
||||
.map(
|
||||
({
|
||||
role,
|
||||
customRoleName,
|
||||
id: roleId,
|
||||
isTemporary,
|
||||
temporaryAccessEndTime
|
||||
}) => {
|
||||
const isExpired =
|
||||
new Date() > new Date(temporaryAccessEndTime || ("" as string));
|
||||
return (
|
||||
<Tag key={roleId}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="capitalize">
|
||||
{formatProjectRoleName(role, customRoleName)}
|
||||
</div>
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
)}
|
||||
{roles.length > MAX_ROLES_TO_BE_SHOWN_IN_TABLE && (
|
||||
<HoverCard>
|
||||
<HoverCardTrigger>
|
||||
<Tag>+{roles.length - MAX_ROLES_TO_BE_SHOWN_IN_TABLE}</Tag>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="border border-gray-700 bg-mineshaft-800 p-4">
|
||||
{roles
|
||||
.slice(MAX_ROLES_TO_BE_SHOWN_IN_TABLE)
|
||||
.map(
|
||||
({
|
||||
role,
|
||||
customRoleName,
|
||||
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>
|
||||
{isTemporary && (
|
||||
<div>
|
||||
<Tooltip
|
||||
content={
|
||||
isExpired ? "Timed role expired" : "Timed role access"
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faClock}
|
||||
className={twMerge(isExpired && "text-red-600")}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<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">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton
|
||||
ariaLabel="Options"
|
||||
colorSchema="secondary"
|
||||
className="w-6"
|
||||
variant="plain"
|
||||
>
|
||||
<FontAwesomeIcon icon={faEllipsisV} />
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent sideOffset={2} align="end">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.Identity, {
|
||||
identityId: id
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
icon={<FontAwesomeIcon icon={faCircleXmark} />}
|
||||
isDisabled={!isAllowed}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
handlePopUpOpen("deleteIdentity", {
|
||||
identityId: id,
|
||||
name
|
||||
});
|
||||
}}
|
||||
>
|
||||
Remove Identity From Project
|
||||
</DropdownMenuItem>
|
||||
{roles.length > MAX_ROLES_TO_BE_SHOWN_IN_TABLE && (
|
||||
<HoverCard>
|
||||
<HoverCardTrigger>
|
||||
<Tag>+{roles.length - MAX_ROLES_TO_BE_SHOWN_IN_TABLE}</Tag>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="border border-gray-700 bg-mineshaft-800 p-4">
|
||||
{roles
|
||||
.slice(MAX_ROLES_TO_BE_SHOWN_IN_TABLE)
|
||||
.map(
|
||||
({
|
||||
role,
|
||||
customRoleName,
|
||||
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>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</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>
|
||||
</motion.div>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
)}
|
||||
</div>
|
||||
</Td>
|
||||
<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">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton
|
||||
ariaLabel="Options"
|
||||
colorSchema="secondary"
|
||||
className="w-6"
|
||||
variant="plain"
|
||||
>
|
||||
<FontAwesomeIcon icon={faEllipsisV} />
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent sideOffset={2} align="end">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.Identity, {
|
||||
identityId: id
|
||||
})}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
icon={<FontAwesomeIcon icon={faCircleXmark} />}
|
||||
isDisabled={!isAllowed}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
handlePopUpOpen("deleteIdentity", {
|
||||
identityId: id,
|
||||
name
|
||||
});
|
||||
}}
|
||||
>
|
||||
Remove Identity From Project
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</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 }
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
|
||||
@@ -7,17 +5,7 @@ import { MembersSection } from "./components";
|
||||
|
||||
export const MembersTab = withProjectPermission(
|
||||
() => {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
return <MembersSection />;
|
||||
},
|
||||
{
|
||||
action: ProjectPermissionActions.Read,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { withProjectPermission } from "@app/hoc";
|
||||
|
||||
@@ -7,17 +5,7 @@ import { ProjectRoleList } from "./components/ProjectRoleList";
|
||||
|
||||
export const ProjectRoleListTab = withProjectPermission(
|
||||
() => {
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
return <ProjectRoleList />;
|
||||
},
|
||||
{ action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.Role }
|
||||
);
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
// import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
// import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import { ServiceTokenSection } from "./components";
|
||||
|
||||
export const ServiceTokenTab = () => {
|
||||
return (
|
||||
<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 }}
|
||||
>
|
||||
<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">
|
||||
<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" />
|
||||
<div className="flex w-full flex-col text-sm">
|
||||
<span className="mb-4 text-lg font-medium">Deprecation Notice</span>
|
||||
@@ -42,8 +34,7 @@ export const ServiceTokenTab = () => {
|
||||
</p>
|
||||
</div>
|
||||
</div> */}
|
||||
<ServiceTokenSection />
|
||||
</div>
|
||||
</motion.div>
|
||||
<ServiceTokenSection />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,9 +21,9 @@ export const AppConnectionsPage = withProjectPermission(
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
</Helmet>
|
||||
<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="project"
|
||||
scope={currentProject.type}
|
||||
className="w-full"
|
||||
title="App Connections"
|
||||
description="Manage project App Connections"
|
||||
|
||||
@@ -8,15 +8,15 @@ export const AuditLogsPage = () => {
|
||||
const { currentProject } = useProject();
|
||||
|
||||
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>
|
||||
<title>Project Audit Logs</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Helmet>
|
||||
<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
|
||||
scope="project"
|
||||
scope={currentProject.type}
|
||||
title="Audit logs"
|
||||
description="Audit logs for security and compliance teams to monitor information access."
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
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 { EmptyState, PageHeader, Spinner } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
|
||||
import { getProjectBaseURL } from "@app/helpers/project";
|
||||
import { useGetWorkspaceGroupMembershipDetails } from "@app/hooks/api/projects/queries";
|
||||
import { ProjectAccessControlTabs } from "@app/types/project";
|
||||
|
||||
import { GroupDetailsSection } from "./components/GroupDetailsSection";
|
||||
import { GroupMembersSection } from "./components/GroupMembersSection";
|
||||
@@ -31,10 +36,27 @@ const Page = () => {
|
||||
);
|
||||
|
||||
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 ? (
|
||||
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||
<PageHeader scope="project" title={groupMembership.group.name} />
|
||||
<div className="mx-auto mb-6 w-full max-w-8xl">
|
||||
<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="mr-4 w-96">
|
||||
<GroupDetailsSection groupMembership={groupMembership} />
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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 { createNotification } from "@app/components/notifications";
|
||||
@@ -28,6 +30,7 @@ import {
|
||||
useGetWorkspaceIdentityMembershipDetails
|
||||
} from "@app/hooks/api";
|
||||
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
||||
import { ProjectAccessControlTabs } from "@app/types/project";
|
||||
|
||||
import { IdentityProjectAdditionalPrivilegeSection } from "./components/IdentityProjectAdditionalPrivilegeSection";
|
||||
import { IdentityRoleDetailsSection } from "./components/IdentityRoleDetailsSection";
|
||||
@@ -113,11 +116,24 @@ const Page = () => {
|
||||
}
|
||||
|
||||
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 ? (
|
||||
<>
|
||||
<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
|
||||
scope="project"
|
||||
scope={currentProject.type}
|
||||
title={identityMembershipDetails?.identity?.name}
|
||||
description={`Identity joined on ${identityMembershipDetails?.createdAt && formatRelative(new Date(identityMembershipDetails?.createdAt || ""), new Date())}`}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
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 { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
@@ -29,6 +31,7 @@ import {
|
||||
useGetWorkspaceUserDetails
|
||||
} from "@app/hooks/api";
|
||||
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
||||
import { ProjectAccessControlTabs } from "@app/types/project";
|
||||
|
||||
import { MemberProjectAdditionalPrivilegeSection } from "./components/MemberProjectAdditionalPrivilegeSection";
|
||||
import { MemberRoleDetailsSection } from "./components/MemberRoleDetailsSection";
|
||||
@@ -115,11 +118,24 @@ export const Page = () => {
|
||||
}
|
||||
|
||||
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 ? (
|
||||
<>
|
||||
<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
|
||||
scope="project"
|
||||
scope={currentProject.type}
|
||||
title={
|
||||
membershipDetails.user.firstName || membershipDetails.user.lastName
|
||||
? `${membershipDetails.user.firstName} ${membershipDetails.user.lastName}`
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
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 { useNavigate, useParams } from "@tanstack/react-router";
|
||||
import { Link, useNavigate, useParams } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -86,20 +92,29 @@ const Page = () => {
|
||||
);
|
||||
|
||||
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 && (
|
||||
<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
|
||||
scope="project"
|
||||
title={
|
||||
<div className="flex flex-col">
|
||||
<div>
|
||||
<span>{data.name}</span>
|
||||
<p className="text-sm leading-3 font-normal text-mineshaft-400 normal-case">
|
||||
{data.slug} {data.description && `- ${data.description}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
scope={currentProject.type}
|
||||
title={data.name}
|
||||
description={
|
||||
<>
|
||||
{data.slug} {data.description && `- ${data.description}`}
|
||||
</>
|
||||
}
|
||||
>
|
||||
{isCustomRole && (
|
||||
|
||||
@@ -35,7 +35,7 @@ export const DeleteProjectProtection = () => {
|
||||
<p className="mb-3 text-xl font-medium">Delete Protection</p>
|
||||
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
|
||||
{(isAllowed) => (
|
||||
<div className="w-max">
|
||||
<div>
|
||||
<Checkbox
|
||||
id="hasDeleteProtection"
|
||||
isDisabled={!isAllowed}
|
||||
@@ -43,6 +43,7 @@ export const DeleteProjectProtection = () => {
|
||||
onCheckedChange={(state) => {
|
||||
handleToggleDeleteProjectProtection(state as boolean);
|
||||
}}
|
||||
allowMultilineLabel
|
||||
>
|
||||
Protects the project from being deleted accidentally. While this option is enabled,
|
||||
you can't delete the project.
|
||||
|
||||
@@ -17,7 +17,7 @@ export const ErrorPage = ({ error }: ErrorComponentProps) => {
|
||||
}
|
||||
|
||||
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">
|
||||
<FontAwesomeIcon icon={faBugs} className="my-2 inline text-6xl" />
|
||||
<p>
|
||||
|
||||
@@ -64,7 +64,7 @@ export const CommitDetailsPage = () => {
|
||||
};
|
||||
|
||||
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]">
|
||||
<ProjectPermissionCan
|
||||
renderGuardBanner
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { CommitWithChanges } from "@app/hooks/api/folderCommits";
|
||||
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 { SecretVersionDiffView } from "../SecretVersionDiffView";
|
||||
@@ -260,7 +261,7 @@ export const CommitDetailsTab = ({
|
||||
Commit History
|
||||
</Button>
|
||||
<PageHeader
|
||||
scope="project"
|
||||
scope={ProjectType.SecretManager}
|
||||
title={`${parsedCommitDetails.changes?.message}` || "No message"}
|
||||
description={
|
||||
<>
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
} from "@app/context/ProjectPermissionContext/types";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useCommitRollback, useGetRollbackPreview } from "@app/hooks/api/folderCommits/queries";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { SecretVersionDiffView } from "../SecretVersionDiffView";
|
||||
|
||||
@@ -297,7 +298,7 @@ export const RollbackPreviewTab = (): JSX.Element => {
|
||||
};
|
||||
|
||||
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
|
||||
renderGuardBanner
|
||||
I={ProjectPermissionCommitsActions.PerformRollback}
|
||||
@@ -307,7 +308,7 @@ export const RollbackPreviewTab = (): JSX.Element => {
|
||||
<div className="h-full w-full">
|
||||
<div>
|
||||
<PageHeader
|
||||
scope="project"
|
||||
scope={ProjectType.SecretManager}
|
||||
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.`}
|
||||
/>
|
||||
|
||||
@@ -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 { PageHeader } from "@app/components/v2";
|
||||
@@ -9,6 +11,7 @@ import {
|
||||
ProjectPermissionCommitsActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/context/ProjectPermissionContext/types";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { CommitHistoryTab } from "./components/CommitHistoryTab";
|
||||
|
||||
@@ -46,10 +49,21 @@ export const CommitsPage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex h-full w-full max-w-7xl justify-center bg-bunker-800 py-4 text-white">
|
||||
<div className="w-full max-w-[75vw]">
|
||||
<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">
|
||||
<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
|
||||
scope="project"
|
||||
scope={ProjectType.SecretManager}
|
||||
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."
|
||||
/>
|
||||
|
||||
@@ -15,7 +15,7 @@ export const IPAllowListPage = () => {
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Helmet>
|
||||
<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">
|
||||
<p className="text-3xl font-medium text-gray-200">IP Allowlist</p>
|
||||
<div />
|
||||
|
||||
@@ -90,11 +90,11 @@ export const IntegrationDetailsByIDPage = () => {
|
||||
<meta property="og:title" content="Manage your .env files in seconds" />
|
||||
<meta name="og:description" content={t("integrations.description") as string} />
|
||||
</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 ? (
|
||||
<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"
|
||||
scope={ProjectType.SecretManager}
|
||||
title={`${integrationSlugNameMapping[integration.integration]} Integration`}
|
||||
>
|
||||
<DropdownMenu>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
|
||||
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
import {
|
||||
@@ -45,21 +46,25 @@ export const IntegrationsListPage = () => {
|
||||
<meta property="og:title" content="Manage your .env files in seconds" />
|
||||
<meta name="og:description" content={t("integrations.description") as string} />
|
||||
</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">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
scope={ProjectType.SecretManager}
|
||||
title="Integrations"
|
||||
description="Manage integrations with third-party services."
|
||||
/>
|
||||
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
|
||||
<Tabs orientation="vertical" value={selectedTab} onValueChange={updateSelectedTab}>
|
||||
<TabList>
|
||||
<Tab value={IntegrationsListPageTabs.SecretSyncs}>Secret Syncs</Tab>
|
||||
<Tab value={IntegrationsListPageTabs.NativeIntegrations}>Native Integrations</Tab>
|
||||
<Tab value={IntegrationsListPageTabs.FrameworkIntegrations}>
|
||||
<Tab variant="project" value={IntegrationsListPageTabs.SecretSyncs}>
|
||||
Secret Syncs
|
||||
</Tab>
|
||||
<Tab variant="project" value={IntegrationsListPageTabs.NativeIntegrations}>
|
||||
Native Integrations
|
||||
</Tab>
|
||||
<Tab variant="project" value={IntegrationsListPageTabs.FrameworkIntegrations}>
|
||||
Framework Integrations
|
||||
</Tab>
|
||||
<Tab value={IntegrationsListPageTabs.InfrastructureIntegrations}>
|
||||
<Tab variant="project" value={IntegrationsListPageTabs.InfrastructureIntegrations}>
|
||||
Infrastructure Integrations
|
||||
</Tab>
|
||||
</TabList>
|
||||
|
||||
@@ -93,7 +93,7 @@ import {
|
||||
import { useGetProjectSecretsOverview } from "@app/hooks/api/dashboard/queries";
|
||||
import { DashboardSecretsOrderBy, ProjectSecretsImportedBy } from "@app/hooks/api/dashboard/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 { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types";
|
||||
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 name="og:description" content={String(t("dashboard.og-description"))} />
|
||||
</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">
|
||||
<PageHeader
|
||||
scope="project"
|
||||
title="Secrets Overview"
|
||||
scope={ProjectType.SecretManager}
|
||||
title="Overview"
|
||||
description={
|
||||
<p className="text-md text-bunker-300">
|
||||
Inject your secrets using
|
||||
@@ -977,7 +977,7 @@ export const OverviewPage = () => {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<div className="flex items-center justify-between">
|
||||
<FolderBreadCrumbs secretPath={secretPath} onResetSearch={handleResetSearch} />
|
||||
<div className="flex flex-row items-center justify-center space-x-2">
|
||||
{isTableFiltered && (
|
||||
|
||||
@@ -46,7 +46,7 @@ export const QuickSearchDynamicSecretItem = ({
|
||||
</span>
|
||||
<span className="text-xs text-mineshaft-400">
|
||||
<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>
|
||||
</Tooltip>
|
||||
</span>
|
||||
|
||||
@@ -32,7 +32,7 @@ export const QuickSearchFolderItem = ({ folderGroup, onClose }: Props) => {
|
||||
>
|
||||
<Td className="w-full whitespace-nowrap">
|
||||
<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>
|
||||
</Tooltip>
|
||||
</Td>
|
||||
|
||||
@@ -126,7 +126,7 @@ export const QuickSearchSecretItem = ({
|
||||
</span>
|
||||
<span className="text-xs text-mineshaft-400">
|
||||
<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>
|
||||
</Tooltip>
|
||||
</span>
|
||||
|
||||
@@ -42,7 +42,7 @@ export const QuickSearchSecretRotationItem = ({ secretRotationGroup, onClose }:
|
||||
</span>
|
||||
<span className="text-xs text-mineshaft-400">
|
||||
<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>
|
||||
</Tooltip>
|
||||
</span>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user