mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 17:27:40 +00:00
feat: completed org breadcrumbs
This commit is contained in:
@@ -0,0 +1,98 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { faChevronRight, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
const Breadcrumb = React.forwardRef<
|
||||||
|
HTMLElement,
|
||||||
|
React.ComponentPropsWithoutRef<"nav"> & {
|
||||||
|
separator?: React.ReactNode;
|
||||||
|
}
|
||||||
|
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
|
||||||
|
Breadcrumb.displayName = "Breadcrumb";
|
||||||
|
|
||||||
|
const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<"ol">>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<ol
|
||||||
|
ref={ref}
|
||||||
|
className={twMerge(
|
||||||
|
"flex flex-wrap items-center gap-1.5 break-words text-sm text-bunker-100 sm:gap-2.5",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
BreadcrumbList.displayName = "BreadcrumbList";
|
||||||
|
|
||||||
|
const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<"li">>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<li ref={ref} className={twMerge("inline-flex items-center gap-1.5", className)} {...props} />
|
||||||
|
)
|
||||||
|
);
|
||||||
|
BreadcrumbItem.displayName = "BreadcrumbItem";
|
||||||
|
|
||||||
|
const BreadcrumbLink = React.forwardRef<
|
||||||
|
HTMLLIElement,
|
||||||
|
React.ComponentPropsWithoutRef<"li"> & {
|
||||||
|
asChild?: boolean;
|
||||||
|
}
|
||||||
|
>(({ asChild, className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
ref={ref}
|
||||||
|
className={twMerge("transition-colors hover:text-bunker-200", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
BreadcrumbLink.displayName = "BreadcrumbLink";
|
||||||
|
|
||||||
|
const BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<"span">>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<span
|
||||||
|
ref={ref}
|
||||||
|
role="link"
|
||||||
|
aria-disabled="true"
|
||||||
|
aria-current="page"
|
||||||
|
className={twMerge("font-normal text-bunker-200 last:text-bunker-300", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
BreadcrumbPage.displayName = "BreadcrumbPage";
|
||||||
|
|
||||||
|
const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => (
|
||||||
|
<li
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
className={twMerge("[&>svg]:h-3.5 [&>svg]:w-3.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children ?? <FontAwesomeIcon icon={faChevronRight} />}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
|
||||||
|
|
||||||
|
const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
|
||||||
|
<span
|
||||||
|
role="presentation"
|
||||||
|
aria-hidden="true"
|
||||||
|
className={twMerge("flex h-9 w-9 items-center justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faEllipsis} className="h-4 w-4" />
|
||||||
|
<span className="sr-only">More</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbEllipsis,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
|
BreadcrumbList,
|
||||||
|
BreadcrumbPage,
|
||||||
|
BreadcrumbSeparator
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./Breadcrumb";
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { ReactNode } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: ReactNode;
|
||||||
|
description?: ReactNode;
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PageHeader = ({ title, description, children }: Props) => (
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="flex w-full justify-between">
|
||||||
|
<div>
|
||||||
|
<h1 className="mr-4 text-3xl font-semibold capitalize text-white">{title}</h1>
|
||||||
|
</div>
|
||||||
|
<div>{children}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="mt-2 text-gray-400">{description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { PageHeader } from "./PageHeader";
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
export * from "./Accordion";
|
export * from "./Accordion";
|
||||||
export * from "./Alert";
|
export * from "./Alert";
|
||||||
export * from "./Badge";
|
export * from "./Badge";
|
||||||
|
export * from "./Breadcrumb";
|
||||||
export * from "./Button";
|
export * from "./Button";
|
||||||
export * from "./Card";
|
export * from "./Card";
|
||||||
export * from "./Checkbox";
|
export * from "./Checkbox";
|
||||||
@@ -21,6 +22,7 @@ export * from "./Input";
|
|||||||
export * from "./Menu";
|
export * from "./Menu";
|
||||||
export * from "./Modal";
|
export * from "./Modal";
|
||||||
export * from "./NoticeBanner";
|
export * from "./NoticeBanner";
|
||||||
|
export * from "./PageHeader";
|
||||||
export * from "./Pagination";
|
export * from "./Pagination";
|
||||||
export * from "./Popoverv2";
|
export * from "./Popoverv2";
|
||||||
export * from "./SecretInput";
|
export * from "./SecretInput";
|
||||||
|
|||||||
@@ -3,14 +3,24 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { faMobile } from "@fortawesome/free-solid-svg-icons";
|
import { faMobile } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { Link, Outlet, useNavigate, useRouter } from "@tanstack/react-router";
|
import { Link, Outlet, useNavigate, useRouter, useRouterState } from "@tanstack/react-router";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { Mfa } from "@app/components/auth/Mfa";
|
import { Mfa } from "@app/components/auth/Mfa";
|
||||||
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
|
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
|
||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
import {
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
|
BreadcrumbList,
|
||||||
|
BreadcrumbPage,
|
||||||
|
BreadcrumbSeparator,
|
||||||
|
Menu,
|
||||||
|
MenuGroup,
|
||||||
|
MenuItem
|
||||||
|
} from "@app/components/v2";
|
||||||
import { useUser } from "@app/context";
|
import { useUser } from "@app/context";
|
||||||
import { usePopUp, useToggle } from "@app/hooks";
|
import { usePopUp, useToggle } from "@app/hooks";
|
||||||
import { useSelectOrganization, workspaceKeys } from "@app/hooks/api";
|
import { useSelectOrganization, workspaceKeys } from "@app/hooks/api";
|
||||||
@@ -32,6 +42,8 @@ export const OrganizationLayout = ({ children }: Props) => {
|
|||||||
const [shouldShowMfa, toggleShowMfa] = useToggle(false);
|
const [shouldShowMfa, toggleShowMfa] = useToggle(false);
|
||||||
const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL);
|
const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL);
|
||||||
const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {});
|
const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {});
|
||||||
|
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
|
||||||
|
const breadcrumbs = matches && "breadcrumbs" in matches ? matches.breadcrumbs : undefined;
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
|
||||||
@@ -252,7 +264,34 @@ export const OrganizationLayout = ({ children }: Props) => {
|
|||||||
isOpen={popUp?.createOrg?.isOpen}
|
isOpen={popUp?.createOrg?.isOpen}
|
||||||
onClose={() => handlePopUpToggle("createOrg", false)}
|
onClose={() => handlePopUpToggle("createOrg", false)}
|
||||||
/>
|
/>
|
||||||
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
|
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 px-4 pb-4 dark:[color-scheme:dark]">
|
||||||
|
{breadcrumbs && (
|
||||||
|
<div className="mx-auto max-w-7xl py-4 capitalize text-white">
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbList>
|
||||||
|
{breadcrumbs.map((el, index) => {
|
||||||
|
const isNotLastCrumb = index + 1 !== breadcrumbs.length;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{el.link && isNotLastCrumb && !("disabled" in el.link) ? (
|
||||||
|
<Link {...el.link}>
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbLink>{el.label}</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbPage>{el.label}</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
)}
|
||||||
|
{isNotLastCrumb && <BreadcrumbSeparator />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</BreadcrumbList>
|
||||||
|
</Breadcrumb>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{children || <Outlet />}
|
{children || <Outlet />}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Helmet } from "react-helmet";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate, useSearch } from "@tanstack/react-router";
|
import { useNavigate, useSearch } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
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 { OrgPermissionActions, OrgPermissionSubjects, useOrgPermission } from "@app/context";
|
import { OrgPermissionActions, OrgPermissionSubjects, useOrgPermission } from "@app/context";
|
||||||
import { OrgAccessControlTabSections } from "@app/types/org";
|
import { OrgAccessControlTabSections } from "@app/types/org";
|
||||||
@@ -59,8 +59,11 @@ export const AccessManagementPage = () => {
|
|||||||
<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 px-6 py-6">
|
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||||
<p className="mb-4 mr-4 text-3xl font-semibold text-white">Organization Access Control</p>
|
<PageHeader
|
||||||
|
title="Organization Access Control"
|
||||||
|
description="Manage fine-grained access for users, groups, roles, and identities within your organization resources."
|
||||||
|
/>
|
||||||
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
|
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
|
||||||
<TabList>
|
<TabList>
|
||||||
{tabSections
|
{tabSections
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||||
import { zodValidator } from "@tanstack/zod-adapter";
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -19,5 +19,16 @@ export const Route = createFileRoute(
|
|||||||
search: {
|
search: {
|
||||||
// strip default values
|
// strip default values
|
||||||
middlewares: [stripSearchParams({ action: "" })]
|
middlewares: [stripSearchParams({ action: "" })]
|
||||||
}
|
},
|
||||||
|
context: () => ({
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "home",
|
||||||
|
link: linkOptions({ to: "/" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "access control"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState } from "react";
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
|
|
||||||
import { OrgAdminProjects } from "./components/OrgAdminProjects";
|
import { OrgAdminProjects } from "./components/OrgAdminProjects";
|
||||||
|
|
||||||
@@ -17,13 +17,13 @@ export const AdminPage = () => {
|
|||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
|
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="flex w-full justify-center bg-bunker-800 py-6 text-white">
|
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||||
<div className="w-full max-w-6xl px-6">
|
<div className="w-full max-w-7xl">
|
||||||
<div className="mb-4">
|
<PageHeader
|
||||||
<p className="text-3xl font-semibold text-gray-200">Organization Admin Console</p>
|
title="Organization Admin Console"
|
||||||
</div>
|
description="View and manage resources across your organization."
|
||||||
|
/>
|
||||||
<Tabs value={activeTab} onValueChange={(el) => setActiveTab(el as TabSections)}>
|
<Tabs value={activeTab} onValueChange={(el) => setActiveTab(el as TabSections)}>
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab value={TabSections.Projects}>Projects</Tab>
|
<Tab value={TabSections.Projects}>Projects</Tab>
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { AdminPage } from "./AdminPage";
|
import { AdminPage } from "./AdminPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/admin"
|
"/_authenticate/_inject-org-details/organization/_layout/admin"
|
||||||
)({
|
)({
|
||||||
component: AdminPage
|
component: AdminPage,
|
||||||
|
context: () => ({
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "home",
|
||||||
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Admin Console"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
|
|
||||||
import { LogsSection } from "./components";
|
import { LogsSection } from "./components";
|
||||||
|
import { PageHeader } from "@app/components/v2";
|
||||||
|
|
||||||
export const AuditLogsPage = () => {
|
export const AuditLogsPage = () => {
|
||||||
return (
|
return (
|
||||||
@@ -11,11 +12,11 @@ export const AuditLogsPage = () => {
|
|||||||
<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 px-6">
|
<div className="w-full max-w-7xl">
|
||||||
<div className="bg-bunker-800 py-6">
|
<PageHeader
|
||||||
<p className="text-3xl font-semibold text-gray-200">Audit Logs</p>
|
title="Audit logs"
|
||||||
<div />
|
description="Audit logs for security and compliance teams to monitor information access."
|
||||||
</div>
|
/>
|
||||||
<LogsSection filterClassName="static py-2" showFilters isOrgAuditLogs showActorColumn />
|
<LogsSection filterClassName="static py-2" showFilters isOrgAuditLogs showActorColumn />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { AuditLogsPage } from "./AuditLogsPage";
|
import { AuditLogsPage } from "./AuditLogsPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/audit-logs"
|
"/_authenticate/_inject-org-details/organization/_layout/audit-logs"
|
||||||
)({
|
)({
|
||||||
component: AuditLogsPage
|
component: AuditLogsPage,
|
||||||
|
context: () => ({
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "home",
|
||||||
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Audit Logs"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const Route = createFileRoute(
|
|||||||
link: linkOptions({ disabled: true, to: "/" })
|
link: linkOptions({ disabled: true, to: "/" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Cert Managers",
|
label: "Cert Management",
|
||||||
link: linkOptions({ to: "/organization/cert-manager/overview" })
|
link: linkOptions({ to: "/organization/cert-manager/overview" })
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { useNavigate, useParams } from "@tanstack/react-router";
|
import { useNavigate, useParams } from "@tanstack/react-router";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
@@ -15,6 +13,7 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
|
PageHeader,
|
||||||
Spinner,
|
Spinner,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
@@ -83,34 +82,17 @@ const Page = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
<div className="container 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 px-6 py-6">
|
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||||
<Button
|
<PageHeader title={data.group.name}>
|
||||||
variant="link"
|
|
||||||
type="submit"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
|
||||||
onClick={() => {
|
|
||||||
navigate({
|
|
||||||
to: "/organization/access-management" as const,
|
|
||||||
search: {
|
|
||||||
selectedTab: TabSections.Groups
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="mb-4"
|
|
||||||
>
|
|
||||||
Groups
|
|
||||||
</Button>
|
|
||||||
<div className="mb-4 flex items-center justify-between">
|
|
||||||
<p className="text-3xl font-semibold text-white">{data.group.name}</p>
|
|
||||||
<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">
|
||||||
<Tooltip content="More options">
|
<Tooltip content="More options">
|
||||||
<FontAwesomeIcon size="sm" icon={faEllipsis} />
|
<Button variant="outline_bg">More</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="end" className="p-1">
|
||||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Groups}>
|
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Groups}>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@@ -153,7 +135,7 @@ const Page = () => {
|
|||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</PageHeader>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="mr-4 w-96">
|
<div className="mr-4 w-96">
|
||||||
<GroupDetailsSection groupId={groupId} handlePopUpOpen={handlePopUpOpen} />
|
<GroupDetailsSection groupId={groupId} handlePopUpOpen={handlePopUpOpen} />
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage";
|
import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/groups/$groupId"
|
"/_authenticate/_inject-org-details/organization/_layout/groups/$groupId"
|
||||||
)({
|
)({
|
||||||
component: GroupDetailsByIDPage
|
component: GroupDetailsByIDPage,
|
||||||
|
context: () => ({
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "home",
|
||||||
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({ to: "/organization/access-management" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "groups"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
+5
-23
@@ -1,8 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { useNavigate, useParams } from "@tanstack/react-router";
|
import { useNavigate, useParams } from "@tanstack/react-router";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
@@ -16,6 +14,7 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
|
PageHeader,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { ROUTE_PATHS } from "@app/const/routes";
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
@@ -165,30 +164,13 @@ const Page = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
<div className="container 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 px-6 py-6">
|
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||||
<Button
|
<PageHeader title={data.identity.name}>
|
||||||
variant="link"
|
|
||||||
type="submit"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
|
||||||
onClick={() => {
|
|
||||||
navigate({
|
|
||||||
to: "/organization/access-management",
|
|
||||||
search: {
|
|
||||||
selectedTab: OrgAccessControlTabSections.Identities
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="mb-4"
|
|
||||||
>
|
|
||||||
Identities
|
|
||||||
</Button>
|
|
||||||
<div className="mb-4 flex items-center justify-between">
|
|
||||||
<p className="text-3xl font-semibold text-white">{data.identity.name}</p>
|
|
||||||
<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">
|
||||||
<Tooltip content="More options">
|
<Tooltip content="More options">
|
||||||
<FontAwesomeIcon size="sm" icon={faEllipsis} />
|
<Button variant="outline_bg">More</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@@ -257,7 +239,7 @@ const Page = () => {
|
|||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</PageHeader>
|
||||||
<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} />
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
|
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/identities/$identityId"
|
"/_authenticate/_inject-org-details/organization/_layout/identities/$identityId"
|
||||||
)({
|
)({
|
||||||
component: IdentityDetailsByIDPage
|
component: IdentityDetailsByIDPage,
|
||||||
|
context: () => ({
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "home",
|
||||||
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({ to: "/organization/access-management" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "identities"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { useNavigate, useParams } from "@tanstack/react-router";
|
import { useNavigate, useParams } from "@tanstack/react-router";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
@@ -14,6 +12,7 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
|
PageHeader,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { ROUTE_PATHS } from "@app/const/routes";
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
@@ -78,31 +77,14 @@ export const Page = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
<div className="container 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 px-6 py-6">
|
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||||
<Button
|
<PageHeader title={data.name}>
|
||||||
variant="link"
|
|
||||||
type="submit"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
|
||||||
onClick={() => {
|
|
||||||
navigate({
|
|
||||||
to: "/organization/access-management" as const,
|
|
||||||
search: {
|
|
||||||
selectedTab: OrgAccessControlTabSections.Roles
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="mb-4"
|
|
||||||
>
|
|
||||||
Roles
|
|
||||||
</Button>
|
|
||||||
<div className="mb-4 flex items-center justify-between">
|
|
||||||
<p className="text-3xl font-semibold text-white">{data.name}</p>
|
|
||||||
{isCustomRole && (
|
{isCustomRole && (
|
||||||
<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">
|
||||||
<Tooltip content="More options">
|
<Tooltip content="More options">
|
||||||
<FontAwesomeIcon size="sm" icon={faEllipsis} />
|
<Button variant="outline_bg">More</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@@ -144,7 +126,7 @@ export const Page = () => {
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
</div>
|
</PageHeader>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="mr-4 w-96">
|
<div className="mr-4 w-96">
|
||||||
<RoleDetailsSection roleId={roleId} handlePopUpOpen={handlePopUpOpen} />
|
<RoleDetailsSection roleId={roleId} handlePopUpOpen={handlePopUpOpen} />
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { RoleByIDPage } from "./RoleByIDPage";
|
import { RoleByIDPage } from "./RoleByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/roles/$roleId"
|
"/_authenticate/_inject-org-details/organization/_layout/roles/$roleId"
|
||||||
)({
|
)({
|
||||||
component: RoleByIDPage
|
component: RoleByIDPage,
|
||||||
|
context: () => ({
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "home",
|
||||||
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({ to: "/organization/access-management" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "RBAC"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
+13
-17
@@ -22,7 +22,15 @@ import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
|||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { OrgPermissionCan } from "@app/components/permissions";
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import { NewProjectModal } from "@app/components/projects";
|
import { NewProjectModal } from "@app/components/projects";
|
||||||
import { Button, IconButton, Input, Pagination, Skeleton, Tooltip } from "@app/components/v2";
|
import {
|
||||||
|
Button,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
PageHeader,
|
||||||
|
Pagination,
|
||||||
|
Skeleton,
|
||||||
|
Tooltip
|
||||||
|
} from "@app/components/v2";
|
||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
@@ -50,13 +58,6 @@ enum ProjectOrderBy {
|
|||||||
Name = "name"
|
Name = "name"
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatTitle = (type: ProjectType) => {
|
|
||||||
if (type === ProjectType.SecretManager) return "Secret Management";
|
|
||||||
if (type === ProjectType.CertificateManager) return "Cert Management";
|
|
||||||
if (type === ProjectType.KMS) return "Key Management";
|
|
||||||
return "SSH";
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDescription = (type: ProjectType) => {
|
const formatDescription = (type: ProjectType) => {
|
||||||
if (type === ProjectType.SecretManager)
|
if (type === ProjectType.SecretManager)
|
||||||
return "Securely store, manage, and rotate various application secrets, such as database credentials, API keys, etc.";
|
return "Securely store, manage, and rotate various application secrets, such as database credentials, API keys, etc.";
|
||||||
@@ -371,7 +372,7 @@ export const ProductOverviewPage = ({ type }: Props) => {
|
|||||||
<link rel="icon" href="/infisical.ico" />
|
<link rel="icon" href="/infisical.ico" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
{!serverDetails?.redisConfigured && (
|
{!serverDetails?.redisConfigured && (
|
||||||
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl">
|
<div className="mb-4 flex flex-col items-start justify-start text-3xl">
|
||||||
<p className="mb-4 mr-4 font-semibold text-white">Announcements</p>
|
<p className="mb-4 mr-4 font-semibold text-white">Announcements</p>
|
||||||
<div className="flex w-full items-center rounded-md border border-blue-400/70 bg-blue-900/70 p-2 text-base text-mineshaft-100">
|
<div className="flex w-full items-center rounded-md border border-blue-400/70 bg-blue-900/70 p-2 text-base text-mineshaft-100">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
@@ -393,14 +394,9 @@ export const ProductOverviewPage = ({ type }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0">
|
<div className="mb-4 flex flex-col items-start justify-start">
|
||||||
<div className="flex w-full justify-between">
|
<PageHeader title="Projects" description={formatDescription(type)} />
|
||||||
<p className="mr-4 text-3xl font-semibold text-white">{formatTitle(type)}</p>
|
<div className="flex w-full flex-row">
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="mr-4 mt-2 text-gray-400">{formatDescription(type)}</p>
|
|
||||||
</div>
|
|
||||||
<div className="mt-6 flex w-full flex-row">
|
|
||||||
<Input
|
<Input
|
||||||
className="h-[2.3rem] bg-mineshaft-800 text-sm placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
|
className="h-[2.3rem] bg-mineshaft-800 text-sm placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
|
||||||
placeholder="Search by project name..."
|
placeholder="Search by project name..."
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const Route = createFileRoute(
|
|||||||
link: linkOptions({ disabled: true, to: "/" })
|
link: linkOptions({ disabled: true, to: "/" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Secret Managers",
|
label: "Secret Management",
|
||||||
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { useSearch } from "@tanstack/react-router";
|
import { useSearch } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { OrgPermissionCan } from "@app/components/permissions";
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, NoticeBanner, Pagination } from "@app/components/v2";
|
import { Button, NoticeBanner, PageHeader, Pagination } from "@app/components/v2";
|
||||||
import { ROUTE_PATHS } from "@app/const/routes";
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
@@ -111,11 +111,11 @@ export const SecretScanningPage = withPermission(
|
|||||||
<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 px-6">
|
<div className="w-full max-w-7xl">
|
||||||
<div className="mt-6 text-3xl font-semibold text-gray-200">Secret Scanning</div>
|
<PageHeader
|
||||||
<div className="mb-6 text-lg text-mineshaft-300">
|
title="Secret Scanning"
|
||||||
Automatically monitor your GitHub activity and prevent secret leaks
|
description="Automatically monitor your GitHub activity and prevent secret leaks"
|
||||||
</div>
|
/>
|
||||||
{config.isSecretScanningDisabled && (
|
{config.isSecretScanningDisabled && (
|
||||||
<NoticeBanner title="Secret scanning is in maintenance" className="mb-4">
|
<NoticeBanner title="Secret scanning is in maintenance" className="mb-4">
|
||||||
We are working on improving the performance of secret scanning due to increased
|
We are working on improving the performance of secret scanning due to increased
|
||||||
@@ -210,4 +210,4 @@ export const SecretScanningPage = withPermission(
|
|||||||
action: OrgPermissionActions.Read,
|
action: OrgPermissionActions.Read,
|
||||||
subject: OrgPermissionSubjects.SecretScanning
|
subject: OrgPermissionSubjects.SecretScanning
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||||
import { zodValidator } from "@tanstack/zod-adapter";
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -12,8 +12,8 @@ const SecretScanningQueryParams = z.object({
|
|||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/secret-scanning"
|
"/_authenticate/_inject-org-details/organization/_layout/secret-scanning"
|
||||||
)({
|
)({
|
||||||
component: SecretScanningPage,
|
|
||||||
validateSearch: zodValidator(SecretScanningQueryParams),
|
validateSearch: zodValidator(SecretScanningQueryParams),
|
||||||
|
component: SecretScanningPage,
|
||||||
search: {
|
search: {
|
||||||
middlewares: [
|
middlewares: [
|
||||||
stripSearchParams({
|
stripSearchParams({
|
||||||
@@ -21,5 +21,16 @@ export const Route = createFileRoute(
|
|||||||
state: ""
|
state: ""
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
context: () => ({
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "home",
|
||||||
|
link: linkOptions({ to: "/" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "secret scanning"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { ShareSecretSection } from "./components";
|
import { ShareSecretSection } from "./components";
|
||||||
|
import { PageHeader } from "@app/components/v2";
|
||||||
|
|
||||||
export const SecretSharingPage = () => {
|
export const SecretSharingPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -18,28 +19,25 @@ 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 px-6 text-white">
|
<div className="container mx-auto h-full w-full max-w-7xl bg-bunker-800 text-white">
|
||||||
<div className="flex items-center justify-between py-6">
|
<PageHeader
|
||||||
<div className="flex w-full flex-col">
|
title="Secret Sharing"
|
||||||
<h2 className="text-3xl font-semibold text-gray-200">Secret Sharing</h2>
|
description="Share secrets securely using a shareable link"
|
||||||
<p className="text-bunker-300">Share secrets securely using a shareable link</p>
|
>
|
||||||
</div>
|
<a
|
||||||
<div className="flex w-max justify-center">
|
target="_blank"
|
||||||
<a
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
href="https://infisical.com/docs/documentation/platform/secret-sharing"
|
||||||
rel="noopener noreferrer"
|
>
|
||||||
href="https://infisical.com/docs/documentation/platform/secret-sharing"
|
<div className="flex w-max cursor-pointer items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
|
||||||
>
|
Documentation{" "}
|
||||||
<div className="flex w-max cursor-pointer items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 text-mineshaft-200 duration-200 hover:border-primary/40 hover:bg-primary/10 hover:text-white">
|
<FontAwesomeIcon
|
||||||
Documentation{" "}
|
icon={faArrowUpRightFromSquare}
|
||||||
<FontAwesomeIcon
|
className="mb-[0.06rem] ml-1 text-xs"
|
||||||
icon={faArrowUpRightFromSquare}
|
/>
|
||||||
className="mb-[0.06rem] ml-1 text-xs"
|
</div>
|
||||||
/>
|
</a>
|
||||||
</div>
|
</PageHeader>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ShareSecretSection />
|
<ShareSecretSection />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { SecretSharingPage } from "./SecretSharingPage";
|
import { SecretSharingPage } from "./SecretSharingPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/secret-sharing"
|
"/_authenticate/_inject-org-details/organization/_layout/secret-sharing"
|
||||||
)({
|
)({
|
||||||
component: SecretSharingPage
|
component: SecretSharingPage,
|
||||||
|
context: () => ({
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "home",
|
||||||
|
link: linkOptions({ to: "/" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "secret sharing"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Helmet } from "react-helmet";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { OrgTabGroup } from "./components";
|
import { OrgTabGroup } from "./components";
|
||||||
|
import { PageHeader } from "@app/components/v2";
|
||||||
|
|
||||||
export const SettingsPage = () => {
|
export const SettingsPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -11,11 +12,9 @@ export const SettingsPage = () => {
|
|||||||
<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="flex w-full justify-center bg-bunker-800 py-6 text-white">
|
<div className="flex w-full justify-center bg-bunker-800 text-white">
|
||||||
<div className="w-full max-w-7xl px-6">
|
<div className="w-full max-w-7xl">
|
||||||
<div className="mb-4">
|
<PageHeader title={t("settings.org.title")} />
|
||||||
<p className="text-3xl font-semibold text-gray-200">{t("settings.org.title")}</p>
|
|
||||||
</div>
|
|
||||||
<OrgTabGroup />
|
<OrgTabGroup />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createFileRoute, stripSearchParams } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
|
||||||
import { zodValidator } from "@tanstack/zod-adapter";
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -15,5 +15,16 @@ export const Route = createFileRoute(
|
|||||||
validateSearch: zodValidator(SettingsPageQueryParams),
|
validateSearch: zodValidator(SettingsPageQueryParams),
|
||||||
search: {
|
search: {
|
||||||
middlewares: [stripSearchParams({ selectedTab: "" })]
|
middlewares: [stripSearchParams({ selectedTab: "" })]
|
||||||
}
|
},
|
||||||
|
context: () => ({
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "home",
|
||||||
|
link: linkOptions({ to: "/" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Settings"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { faChevronLeft, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { useNavigate, useParams } from "@tanstack/react-router";
|
import { useNavigate, useParams } from "@tanstack/react-router";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
@@ -15,6 +13,7 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
|
PageHeader,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { ROUTE_PATHS } from "@app/const/routes";
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
@@ -118,143 +117,132 @@ const Page = withPermission(
|
|||||||
return (
|
return (
|
||||||
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
<div className="container 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 px-6 py-6">
|
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||||
<Button
|
<PageHeader
|
||||||
variant="link"
|
title={
|
||||||
type="submit"
|
membership.user.firstName || membership.user.lastName
|
||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
|
||||||
onClick={() => {
|
|
||||||
navigate({
|
|
||||||
to: "/organization/access-management" as const,
|
|
||||||
search: {
|
|
||||||
selectedTab: OrgAccessControlTabSections.Member
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="mb-4"
|
|
||||||
>
|
|
||||||
Users
|
|
||||||
</Button>
|
|
||||||
<div className="mb-4 flex items-center justify-between">
|
|
||||||
<p className="text-3xl font-semibold text-white">
|
|
||||||
{membership.user.firstName || membership.user.lastName
|
|
||||||
? `${membership.user.firstName} ${membership.user.lastName ?? ""}`.trim()
|
? `${membership.user.firstName} ${membership.user.lastName ?? ""}`.trim()
|
||||||
: "-"}
|
: "-"
|
||||||
</p>
|
}
|
||||||
{userId !== membership.user.id && (
|
>
|
||||||
<DropdownMenu>
|
<div>
|
||||||
<DropdownMenuTrigger asChild className="rounded-lg">
|
{userId !== membership.user.id && (
|
||||||
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
<DropdownMenu>
|
||||||
<Tooltip content="More options">
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
<FontAwesomeIcon size="sm" icon={faEllipsis} />
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||||
</Tooltip>
|
<Tooltip content="More options">
|
||||||
</div>
|
<Button variant="outline_bg" size="sm">
|
||||||
</DropdownMenuTrigger>
|
More
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
</Button>
|
||||||
<OrgPermissionCan
|
</Tooltip>
|
||||||
I={OrgPermissionActions.Edit}
|
</div>
|
||||||
a={OrgPermissionSubjects.Identity}
|
</DropdownMenuTrigger>
|
||||||
>
|
<DropdownMenuContent align="end" className="p-1">
|
||||||
{(isAllowed) => (
|
<OrgPermissionCan
|
||||||
<DropdownMenuItem
|
I={OrgPermissionActions.Edit}
|
||||||
className={twMerge(
|
a={OrgPermissionSubjects.Identity}
|
||||||
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
>
|
||||||
)}
|
{(isAllowed) => (
|
||||||
onClick={() =>
|
<DropdownMenuItem
|
||||||
handlePopUpOpen("orgMembership", {
|
className={twMerge(
|
||||||
membershipId: membership.id,
|
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
role: membership.role,
|
)}
|
||||||
roleId: membership.roleId
|
onClick={() =>
|
||||||
})
|
handlePopUpOpen("orgMembership", {
|
||||||
}
|
membershipId: membership.id,
|
||||||
disabled={!isAllowed}
|
role: membership.role,
|
||||||
>
|
roleId: membership.roleId
|
||||||
Edit User
|
})
|
||||||
</DropdownMenuItem>
|
|
||||||
)}
|
|
||||||
</OrgPermissionCan>
|
|
||||||
<OrgPermissionCan
|
|
||||||
I={OrgPermissionActions.Delete}
|
|
||||||
a={OrgPermissionSubjects.Member}
|
|
||||||
>
|
|
||||||
{(isAllowed) => (
|
|
||||||
<DropdownMenuItem
|
|
||||||
className={
|
|
||||||
membership.isActive
|
|
||||||
? twMerge(
|
|
||||||
isAllowed
|
|
||||||
? "hover:!bg-red-500 hover:!text-white"
|
|
||||||
: "pointer-events-none cursor-not-allowed opacity-50"
|
|
||||||
)
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
onClick={async () => {
|
|
||||||
if (currentOrg?.scimEnabled) {
|
|
||||||
createNotification({
|
|
||||||
text: "You cannot manage users from Infisical when SCIM is enabled for your organization",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
disabled={!isAllowed}
|
||||||
if (!membership.isActive) {
|
>
|
||||||
// activate user
|
Edit User
|
||||||
await updateOrgMembership({
|
</DropdownMenuItem>
|
||||||
organizationId: orgId,
|
)}
|
||||||
membershipId,
|
</OrgPermissionCan>
|
||||||
isActive: true
|
<OrgPermissionCan
|
||||||
});
|
I={OrgPermissionActions.Delete}
|
||||||
|
a={OrgPermissionSubjects.Member}
|
||||||
return;
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={
|
||||||
|
membership.isActive
|
||||||
|
? twMerge(
|
||||||
|
isAllowed
|
||||||
|
? "hover:!bg-red-500 hover:!text-white"
|
||||||
|
: "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)
|
||||||
|
: ""
|
||||||
}
|
}
|
||||||
|
onClick={async () => {
|
||||||
|
if (currentOrg?.scimEnabled) {
|
||||||
|
createNotification({
|
||||||
|
text: "You cannot manage users from Infisical when SCIM is enabled for your organization",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// deactivate user
|
if (!membership.isActive) {
|
||||||
handlePopUpOpen("deactivateMember", {
|
// activate user
|
||||||
orgMembershipId: membershipId,
|
await updateOrgMembership({
|
||||||
username: membership.user.username
|
organizationId: orgId,
|
||||||
});
|
membershipId,
|
||||||
}}
|
isActive: true
|
||||||
disabled={!isAllowed}
|
});
|
||||||
>
|
|
||||||
{`${membership.isActive ? "Deactivate" : "Activate"} User`}
|
return;
|
||||||
</DropdownMenuItem>
|
}
|
||||||
)}
|
|
||||||
</OrgPermissionCan>
|
// deactivate user
|
||||||
<OrgPermissionCan
|
handlePopUpOpen("deactivateMember", {
|
||||||
I={OrgPermissionActions.Delete}
|
orgMembershipId: membershipId,
|
||||||
a={OrgPermissionSubjects.Member}
|
username: membership.user.username
|
||||||
>
|
|
||||||
{(isAllowed) => (
|
|
||||||
<DropdownMenuItem
|
|
||||||
className={twMerge(
|
|
||||||
isAllowed
|
|
||||||
? "hover:!bg-red-500 hover:!text-white"
|
|
||||||
: "pointer-events-none cursor-not-allowed opacity-50"
|
|
||||||
)}
|
|
||||||
onClick={() => {
|
|
||||||
if (currentOrg?.scimEnabled) {
|
|
||||||
createNotification({
|
|
||||||
text: "You cannot manage users from Infisical when SCIM is enabled for your organization",
|
|
||||||
type: "error"
|
|
||||||
});
|
});
|
||||||
return;
|
}}
|
||||||
}
|
disabled={!isAllowed}
|
||||||
|
>
|
||||||
|
{`${membership.isActive ? "Deactivate" : "Activate"} User`}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</OrgPermissionCan>
|
||||||
|
<OrgPermissionCan
|
||||||
|
I={OrgPermissionActions.Delete}
|
||||||
|
a={OrgPermissionSubjects.Member}
|
||||||
|
>
|
||||||
|
{(isAllowed) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={twMerge(
|
||||||
|
isAllowed
|
||||||
|
? "hover:!bg-red-500 hover:!text-white"
|
||||||
|
: "pointer-events-none cursor-not-allowed opacity-50"
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
if (currentOrg?.scimEnabled) {
|
||||||
|
createNotification({
|
||||||
|
text: "You cannot manage users from Infisical when SCIM is enabled for your organization",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
handlePopUpOpen("removeMember", {
|
handlePopUpOpen("removeMember", {
|
||||||
orgMembershipId: membershipId,
|
orgMembershipId: membershipId,
|
||||||
username: membership.user.username
|
username: membership.user.username
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
disabled={!isAllowed}
|
disabled={!isAllowed}
|
||||||
>
|
>
|
||||||
Remove User
|
Remove User
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</PageHeader>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="mr-4 w-96">
|
<div className="mr-4 w-96">
|
||||||
<UserDetailsSection membershipId={membershipId} handlePopUpOpen={handlePopUpOpen} />
|
<UserDetailsSection membershipId={membershipId} handlePopUpOpen={handlePopUpOpen} />
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { UserDetailsByIDPage } from "./UserDetailsByIDPage";
|
import { UserDetailsByIDPage } from "./UserDetailsByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/members/$membershipId"
|
"/_authenticate/_inject-org-details/organization/_layout/members/$membershipId"
|
||||||
)({
|
)({
|
||||||
component: UserDetailsByIDPage
|
component: UserDetailsByIDPage,
|
||||||
|
context: () => ({
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "home",
|
||||||
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({ to: "/organization/access-management" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Users"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user