feat: completed org breadcrumbs

This commit is contained in:
=
2025-01-15 19:03:24 +05:30
parent 338961480c
commit 1b1fe2a700
29 changed files with 514 additions and 295 deletions

View File

@@ -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
};

View File

@@ -0,0 +1 @@
export * from "./Breadcrumb";

View File

@@ -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>
);

View File

@@ -0,0 +1 @@
export { PageHeader } from "./PageHeader";

View File

@@ -2,6 +2,7 @@
export * from "./Accordion";
export * from "./Alert";
export * from "./Badge";
export * from "./Breadcrumb";
export * from "./Button";
export * from "./Card";
export * from "./Checkbox";
@@ -21,6 +22,7 @@ export * from "./Input";
export * from "./Menu";
export * from "./Modal";
export * from "./NoticeBanner";
export * from "./PageHeader";
export * from "./Pagination";
export * from "./Popoverv2";
export * from "./SecretInput";

View File

@@ -3,14 +3,24 @@ import { useTranslation } from "react-i18next";
import { faMobile } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
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 { twMerge } from "tailwind-merge";
import { Mfa } from "@app/components/auth/Mfa";
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
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 { usePopUp, useToggle } from "@app/hooks";
import { useSelectOrganization, workspaceKeys } from "@app/hooks/api";
@@ -32,6 +42,8 @@ export const OrganizationLayout = ({ children }: Props) => {
const [shouldShowMfa, toggleShowMfa] = useToggle(false);
const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL);
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();
@@ -252,7 +264,34 @@ export const OrganizationLayout = ({ children }: Props) => {
isOpen={popUp?.createOrg?.isOpen}
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 />}
</main>
</div>

View File

@@ -2,7 +2,7 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
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 { OrgPermissionActions, OrgPermissionSubjects, useOrgPermission } from "@app/context";
import { OrgAccessControlTabSections } from "@app/types/org";
@@ -59,8 +59,11 @@ export const AccessManagementPage = () => {
<Helmet>
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
</Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6">
<p className="mb-4 mr-4 text-3xl font-semibold text-white">Organization Access Control</p>
<div className="mx-auto mb-6 w-full max-w-7xl">
<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}>
<TabList>
{tabSections

View File

@@ -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 { z } from "zod";
@@ -19,5 +19,16 @@ export const Route = createFileRoute(
search: {
// strip default values
middlewares: [stripSearchParams({ action: "" })]
}
},
context: () => ({
breadcrumbs: [
{
label: "home",
link: linkOptions({ to: "/" })
},
{
label: "access control"
}
]
})
});

View File

@@ -2,7 +2,7 @@ import { useState } from "react";
import { Helmet } from "react-helmet";
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";
@@ -17,13 +17,13 @@ export const AdminPage = () => {
<>
<Helmet>
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Helmet>
<div className="flex w-full justify-center bg-bunker-800 py-6 text-white">
<div className="w-full max-w-6xl px-6">
<div className="mb-4">
<p className="text-3xl font-semibold text-gray-200">Organization Admin Console</p>
</div>
<div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl">
<PageHeader
title="Organization Admin Console"
description="View and manage resources across your organization."
/>
<Tabs value={activeTab} onValueChange={(el) => setActiveTab(el as TabSections)}>
<TabList>
<Tab value={TabSections.Projects}>Projects</Tab>

View File

@@ -1,9 +1,20 @@
import { createFileRoute } from "@tanstack/react-router";
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { AdminPage } from "./AdminPage";
export const Route = createFileRoute(
"/_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"
}
]
})
});

View File

@@ -1,6 +1,7 @@
import { Helmet } from "react-helmet";
import { LogsSection } from "./components";
import { PageHeader } from "@app/components/v2";
export const AuditLogsPage = () => {
return (
@@ -11,11 +12,11 @@ export const AuditLogsPage = () => {
<meta property="og:image" content="/images/message.png" />
</Helmet>
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl px-6">
<div className="bg-bunker-800 py-6">
<p className="text-3xl font-semibold text-gray-200">Audit Logs</p>
<div />
</div>
<div className="w-full max-w-7xl">
<PageHeader
title="Audit logs"
description="Audit logs for security and compliance teams to monitor information access."
/>
<LogsSection filterClassName="static py-2" showFilters isOrgAuditLogs showActorColumn />
</div>
</div>

View File

@@ -1,9 +1,20 @@
import { createFileRoute } from "@tanstack/react-router";
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { AuditLogsPage } from "./AuditLogsPage";
export const Route = createFileRoute(
"/_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"
}
]
})
});

View File

@@ -13,7 +13,7 @@ export const Route = createFileRoute(
link: linkOptions({ disabled: true, to: "/" })
},
{
label: "Cert Managers",
label: "Cert Management",
link: linkOptions({ to: "/organization/cert-manager/overview" })
}
]

View File

@@ -1,7 +1,5 @@
import { Helmet } from "react-helmet";
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 { twMerge } from "tailwind-merge";
@@ -15,6 +13,7 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
PageHeader,
Spinner,
Tooltip
} from "@app/components/v2";
@@ -83,34 +82,17 @@ const Page = () => {
return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && (
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6">
<Button
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>
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title={data.group.name}>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options">
<FontAwesomeIcon size="sm" icon={faEllipsis} />
<Button variant="outline_bg">More</Button>
</Tooltip>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<DropdownMenuContent align="end" className="p-1">
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Groups}>
{(isAllowed) => (
<DropdownMenuItem
@@ -153,7 +135,7 @@ const Page = () => {
</OrgPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</div>
</PageHeader>
<div className="flex">
<div className="mr-4 w-96">
<GroupDetailsSection groupId={groupId} handlePopUpOpen={handlePopUpOpen} />

View File

@@ -1,9 +1,24 @@
import { createFileRoute } from "@tanstack/react-router";
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage";
export const Route = createFileRoute(
"/_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"
}
]
})
});

View File

@@ -1,8 +1,6 @@
import { useState } from "react";
import { Helmet } from "react-helmet";
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 { twMerge } from "tailwind-merge";
@@ -16,6 +14,7 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
PageHeader,
Tooltip
} from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
@@ -165,30 +164,13 @@ const Page = () => {
return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && (
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6">
<Button
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>
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title={data.identity.name}>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options">
<FontAwesomeIcon size="sm" icon={faEllipsis} />
<Button variant="outline_bg">More</Button>
</Tooltip>
</div>
</DropdownMenuTrigger>
@@ -257,7 +239,7 @@ const Page = () => {
</OrgPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</div>
</PageHeader>
<div className="flex">
<div className="mr-4 w-96">
<IdentityDetailsSection identityId={identityId} handlePopUpOpen={handlePopUpOpen} />

View File

@@ -1,9 +1,24 @@
import { createFileRoute } from "@tanstack/react-router";
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
export const Route = createFileRoute(
"/_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"
}
]
})
});

View File

@@ -1,7 +1,5 @@
import { Helmet } from "react-helmet";
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 { twMerge } from "tailwind-merge";
@@ -14,6 +12,7 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
PageHeader,
Tooltip
} from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
@@ -78,31 +77,14 @@ export const Page = () => {
return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{data && (
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6">
<Button
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>
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title={data.name}>
{isCustomRole && (
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options">
<FontAwesomeIcon size="sm" icon={faEllipsis} />
<Button variant="outline_bg">More</Button>
</Tooltip>
</div>
</DropdownMenuTrigger>
@@ -144,7 +126,7 @@ export const Page = () => {
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</PageHeader>
<div className="flex">
<div className="mr-4 w-96">
<RoleDetailsSection roleId={roleId} handlePopUpOpen={handlePopUpOpen} />

View File

@@ -1,9 +1,24 @@
import { createFileRoute } from "@tanstack/react-router";
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { RoleByIDPage } from "./RoleByIDPage";
export const Route = createFileRoute(
"/_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"
}
]
})
});

View File

@@ -22,7 +22,15 @@ import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
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 {
OrgPermissionActions,
OrgPermissionSubjects,
@@ -50,13 +58,6 @@ enum ProjectOrderBy {
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) => {
if (type === ProjectType.SecretManager)
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" />
</Helmet>
{!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>
<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
@@ -393,14 +394,9 @@ export const ProductOverviewPage = ({ type }: Props) => {
</div>
</div>
)}
<div className="mb-4 flex flex-col items-start justify-start px-6 py-6 pb-0">
<div className="flex w-full justify-between">
<p className="mr-4 text-3xl font-semibold text-white">{formatTitle(type)}</p>
</div>
<div>
<p className="mr-4 mt-2 text-gray-400">{formatDescription(type)}</p>
</div>
<div className="mt-6 flex w-full flex-row">
<div className="mb-4 flex flex-col items-start justify-start">
<PageHeader title="Projects" description={formatDescription(type)} />
<div className="flex w-full flex-row">
<Input
className="h-[2.3rem] bg-mineshaft-800 text-sm placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
placeholder="Search by project name..."

View File

@@ -13,7 +13,7 @@ export const Route = createFileRoute(
link: linkOptions({ disabled: true, to: "/" })
},
{
label: "Secret Managers",
label: "Secret Management",
link: linkOptions({ to: "/organization/secret-manager/overview" })
}
]

View File

@@ -5,7 +5,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { useSearch } from "@tanstack/react-router";
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 {
OrgPermissionActions,
@@ -111,11 +111,11 @@ export const SecretScanningPage = withPermission(
<meta property="og:image" content="/images/message.png" />
</Helmet>
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl px-6">
<div className="mt-6 text-3xl font-semibold text-gray-200">Secret Scanning</div>
<div className="mb-6 text-lg text-mineshaft-300">
Automatically monitor your GitHub activity and prevent secret leaks
</div>
<div className="w-full max-w-7xl">
<PageHeader
title="Secret Scanning"
description="Automatically monitor your GitHub activity and prevent secret leaks"
/>
{config.isSecretScanningDisabled && (
<NoticeBanner title="Secret scanning is in maintenance" className="mb-4">
We are working on improving the performance of secret scanning due to increased
@@ -210,4 +210,4 @@ export const SecretScanningPage = withPermission(
action: OrgPermissionActions.Read,
subject: OrgPermissionSubjects.SecretScanning
}
);
);

View File

@@ -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 { z } from "zod";
@@ -12,8 +12,8 @@ const SecretScanningQueryParams = z.object({
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/organization/_layout/secret-scanning"
)({
component: SecretScanningPage,
validateSearch: zodValidator(SecretScanningQueryParams),
component: SecretScanningPage,
search: {
middlewares: [
stripSearchParams({
@@ -21,5 +21,16 @@ export const Route = createFileRoute(
state: ""
})
]
}
},
context: () => ({
breadcrumbs: [
{
label: "home",
link: linkOptions({ to: "/" })
},
{
label: "secret scanning"
}
]
})
});

View File

@@ -4,6 +4,7 @@ import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ShareSecretSection } from "./components";
import { PageHeader } from "@app/components/v2";
export const SecretSharingPage = () => {
const { t } = useTranslation();
@@ -18,28 +19,25 @@ export const SecretSharingPage = () => {
<meta name="og:description" content={String(t("approval.og-description"))} />
</Helmet>
<div className="h-full">
<div className="container mx-auto h-full w-full max-w-7xl bg-bunker-800 px-6 text-white">
<div className="flex items-center justify-between py-6">
<div className="flex w-full flex-col">
<h2 className="text-3xl font-semibold text-gray-200">Secret Sharing</h2>
<p className="text-bunker-300">Share secrets securely using a shareable link</p>
</div>
<div className="flex w-max justify-center">
<a
target="_blank"
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{" "}
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] ml-1 text-xs"
/>
</div>
</a>
</div>
</div>
<div className="container mx-auto h-full w-full max-w-7xl bg-bunker-800 text-white">
<PageHeader
title="Secret Sharing"
description="Share secrets securely using a shareable link"
>
<a
target="_blank"
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{" "}
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] ml-1 text-xs"
/>
</div>
</a>
</PageHeader>
<ShareSecretSection />
</div>
</div>

View File

@@ -1,9 +1,20 @@
import { createFileRoute } from "@tanstack/react-router";
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { SecretSharingPage } from "./SecretSharingPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/organization/_layout/secret-sharing"
)({
component: SecretSharingPage
component: SecretSharingPage,
context: () => ({
breadcrumbs: [
{
label: "home",
link: linkOptions({ to: "/" })
},
{
label: "secret sharing"
}
]
})
});

View File

@@ -2,6 +2,7 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { OrgTabGroup } from "./components";
import { PageHeader } from "@app/components/v2";
export const SettingsPage = () => {
const { t } = useTranslation();
@@ -11,11 +12,9 @@ export const SettingsPage = () => {
<Helmet>
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
</Helmet>
<div className="flex w-full justify-center bg-bunker-800 py-6 text-white">
<div className="w-full max-w-7xl px-6">
<div className="mb-4">
<p className="text-3xl font-semibold text-gray-200">{t("settings.org.title")}</p>
</div>
<div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl">
<PageHeader title={t("settings.org.title")} />
<OrgTabGroup />
</div>
</div>

View File

@@ -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 { z } from "zod";
@@ -15,5 +15,16 @@ export const Route = createFileRoute(
validateSearch: zodValidator(SettingsPageQueryParams),
search: {
middlewares: [stripSearchParams({ selectedTab: "" })]
}
},
context: () => ({
breadcrumbs: [
{
label: "home",
link: linkOptions({ to: "/" })
},
{
label: "Settings"
}
]
})
});

View File

@@ -1,7 +1,5 @@
import { Helmet } from "react-helmet";
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 { twMerge } from "tailwind-merge";
@@ -15,6 +13,7 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
PageHeader,
Tooltip
} from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
@@ -118,143 +117,132 @@ const Page = withPermission(
return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
{membership && (
<div className="mx-auto mb-6 w-full max-w-7xl px-6 py-6">
<Button
variant="link"
type="submit"
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
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader
title={
membership.user.firstName || membership.user.lastName
? `${membership.user.firstName} ${membership.user.lastName ?? ""}`.trim()
: "-"}
</p>
{userId !== membership.user.id && (
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options">
<FontAwesomeIcon size="sm" icon={faEllipsis} />
</Tooltip>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={() =>
handlePopUpOpen("orgMembership", {
membershipId: membership.id,
role: membership.role,
roleId: membership.roleId
})
}
disabled={!isAllowed}
>
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;
: "-"
}
>
<div>
{userId !== membership.user.id && (
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options">
<Button variant="outline_bg" size="sm">
More
</Button>
</Tooltip>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="p-1">
<OrgPermissionCan
I={OrgPermissionActions.Edit}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={() =>
handlePopUpOpen("orgMembership", {
membershipId: membership.id,
role: membership.role,
roleId: membership.roleId
})
}
if (!membership.isActive) {
// activate user
await updateOrgMembership({
organizationId: orgId,
membershipId,
isActive: true
});
return;
disabled={!isAllowed}
>
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;
}
// deactivate user
handlePopUpOpen("deactivateMember", {
orgMembershipId: membershipId,
username: membership.user.username
});
}}
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"
if (!membership.isActive) {
// activate user
await updateOrgMembership({
organizationId: orgId,
membershipId,
isActive: true
});
return;
}
// deactivate user
handlePopUpOpen("deactivateMember", {
orgMembershipId: membershipId,
username: membership.user.username
});
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", {
orgMembershipId: membershipId,
username: membership.user.username
});
}}
disabled={!isAllowed}
>
Remove User
</DropdownMenuItem>
)}
</OrgPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
handlePopUpOpen("removeMember", {
orgMembershipId: membershipId,
username: membership.user.username
});
}}
disabled={!isAllowed}
>
Remove User
</DropdownMenuItem>
)}
</OrgPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</PageHeader>
<div className="flex">
<div className="mr-4 w-96">
<UserDetailsSection membershipId={membershipId} handlePopUpOpen={handlePopUpOpen} />

View File

@@ -1,9 +1,24 @@
import { createFileRoute } from "@tanstack/react-router";
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { UserDetailsByIDPage } from "./UserDetailsByIDPage";
export const Route = createFileRoute(
"/_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"
}
]
})
});