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