mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 16:27:40 +00:00
feat: finished breadcrumbs for all project types except secret manager
This commit is contained in:
@@ -1,47 +1,34 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
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 { Link, Outlet, useRouterState } from "@tanstack/react-router";
|
||||||
import { Link, Outlet, useNavigate, useRouter } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
import { Mfa } from "@app/components/auth/Mfa";
|
|
||||||
import SecurityClient from "@app/components/utilities/SecurityClient";
|
|
||||||
import { Menu, MenuGroup, MenuItem } from "@app/components/v2";
|
|
||||||
import { useUser, useWorkspace } from "@app/context";
|
|
||||||
import { useToggle } from "@app/hooks";
|
|
||||||
import {
|
import {
|
||||||
useGetAccessRequestsCount,
|
Breadcrumb,
|
||||||
useGetSecretApprovalRequestCount,
|
BreadcrumbItem,
|
||||||
useSelectOrganization,
|
BreadcrumbLink,
|
||||||
workspaceKeys
|
BreadcrumbList,
|
||||||
} from "@app/hooks/api";
|
BreadcrumbPage,
|
||||||
import { authKeys } from "@app/hooks/api/auth/queries";
|
BreadcrumbSeparator,
|
||||||
import { MfaMethod } from "@app/hooks/api/auth/types";
|
Menu,
|
||||||
|
MenuGroup,
|
||||||
|
MenuItem
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useWorkspace } from "@app/context";
|
||||||
|
import { useGetAccessRequestsCount, useGetSecretApprovalRequestCount } from "@app/hooks/api";
|
||||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
|
|
||||||
|
|
||||||
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
|
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
|
||||||
import { ProjectSelect } from "./components/ProjectSelect";
|
import { ProjectSelect } from "./components/ProjectSelect";
|
||||||
import { SidebarHeader } from "./components/SidebarHeader";
|
|
||||||
|
|
||||||
// This is a generic layout shared by all types of projects.
|
// This is a generic layout shared by all types of projects.
|
||||||
// If the product layout differs significantly, create a new layout as needed.
|
// If the product layout differs significantly, create a new layout as needed.
|
||||||
export const ProjectLayout = () => {
|
export const ProjectLayout = () => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const navigate = useNavigate();
|
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
|
||||||
|
const breadcrumbs = matches && "breadcrumbs" in matches ? matches.breadcrumbs : undefined;
|
||||||
const [shouldShowMfa, toggleShowMfa] = useToggle(false);
|
|
||||||
const [requiredMfaMethod, setRequiredMfaMethod] = useState(MfaMethod.EMAIL);
|
|
||||||
const [mfaSuccessCallback, setMfaSuccessCallback] = useState<() => void>(() => {});
|
|
||||||
|
|
||||||
const { user } = useUser();
|
|
||||||
|
|
||||||
const { mutateAsync: selectOrganization } = useSelectOrganization();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const workspaceId = currentWorkspace?.id || "";
|
const workspaceId = currentWorkspace?.id || "";
|
||||||
const projectSlug = currentWorkspace?.slug || "";
|
const projectSlug = currentWorkspace?.slug || "";
|
||||||
|
|
||||||
@@ -62,40 +49,6 @@ export const ProjectLayout = () => {
|
|||||||
const pendingRequestsCount =
|
const pendingRequestsCount =
|
||||||
(secretApprovalReqCount?.open || 0) + (accessApprovalRequestCount?.pendingCount || 0);
|
(secretApprovalReqCount?.open || 0) + (accessApprovalRequestCount?.pendingCount || 0);
|
||||||
|
|
||||||
const handleOrgChange = async (orgId: string) => {
|
|
||||||
queryClient.removeQueries({ queryKey: authKeys.getAuthToken });
|
|
||||||
queryClient.removeQueries({ queryKey: workspaceKeys.getAllUserWorkspace() });
|
|
||||||
|
|
||||||
const { token, isMfaEnabled, mfaMethod } = await selectOrganization({
|
|
||||||
organizationId: orgId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isMfaEnabled) {
|
|
||||||
SecurityClient.setMfaToken(token);
|
|
||||||
if (mfaMethod) {
|
|
||||||
setRequiredMfaMethod(mfaMethod);
|
|
||||||
}
|
|
||||||
toggleShowMfa.on();
|
|
||||||
setMfaSuccessCallback(() => () => handleOrgChange(orgId));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await router.invalidate();
|
|
||||||
await navigateUserToOrg(navigate, orgId);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (shouldShowMfa) {
|
|
||||||
return (
|
|
||||||
<div className="flex max-h-screen min-h-screen flex-col items-center justify-center gap-2 overflow-y-auto bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700">
|
|
||||||
<Mfa
|
|
||||||
email={user.email as string}
|
|
||||||
method={requiredMfaMethod}
|
|
||||||
successCallback={mfaSuccessCallback}
|
|
||||||
closeMfa={() => toggleShowMfa.off()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 md:flex">
|
||||||
@@ -247,6 +200,33 @@ export const ProjectLayout = () => {
|
|||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
<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 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>
|
||||||
|
)}
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+6
-22
@@ -13,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";
|
||||||
@@ -80,34 +81,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.friendlyName}>
|
||||||
variant="link"
|
|
||||||
type="submit"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
|
||||||
onClick={() =>
|
|
||||||
navigate({
|
|
||||||
to: `/${ProjectType.CertificateManager}/$projectId/overview` as const,
|
|
||||||
params: {
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="mb-4"
|
|
||||||
>
|
|
||||||
Certificate Authorities
|
|
||||||
</Button>
|
|
||||||
<div className="mb-4 flex items-center justify-between">
|
|
||||||
<p className="text-3xl font-semibold text-white">{data.friendlyName}</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="emd" className="p-1">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Delete}
|
I={ProjectPermissionActions.Delete}
|
||||||
a={ProjectPermissionSub.CertificateAuthorities}
|
a={ProjectPermissionSub.CertificateAuthorities}
|
||||||
@@ -133,7 +117,7 @@ const Page = () => {
|
|||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</PageHeader>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="mr-4 w-96">
|
<div className="mr-4 w-96">
|
||||||
<CaDetailsSection caId={caId} handlePopUpOpen={handlePopUpOpen} />
|
<CaDetailsSection caId={caId} handlePopUpOpen={handlePopUpOpen} />
|
||||||
|
|||||||
@@ -1,9 +1,25 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { CertAuthDetailsByIDPage } from "./CertAuthDetailsByIDPage";
|
import { CertAuthDetailsByIDPage } from "./CertAuthDetailsByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId"
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/ca/$caId"
|
||||||
)({
|
)({
|
||||||
component: CertAuthDetailsByIDPage
|
component: CertAuthDetailsByIDPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Certificate Authorities",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/cert-manager/$projectId/overview",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Helmet } from "react-helmet";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
|
|
||||||
import { CaTab, CertificatesTab, PkiAlertsTab } from "./components";
|
import { CaTab, CertificatesTab, PkiAlertsTab } from "./components";
|
||||||
@@ -19,11 +19,9 @@ export const CertificatesPage = () => {
|
|||||||
<div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white">
|
<div className="container 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>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
|
||||||
<meta property="og:image" content="/images/message.png" />
|
|
||||||
</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">Internal PKI</p>
|
<PageHeader title="Overview" />
|
||||||
<Tabs defaultValue={TabSections.Certificates}>
|
<Tabs defaultValue={TabSections.Certificates}>
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab value={TabSections.Certificates}>Certificates</Tab>
|
<Tab value={TabSections.Certificates}>Certificates</Tab>
|
||||||
|
|||||||
+6
-24
@@ -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";
|
||||||
@@ -74,34 +73,17 @@ export const PkiCollectionPage = () => {
|
|||||||
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: `/${ProjectType.CertificateManager}/$projectId/overview` as const,
|
|
||||||
params: {
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="mb-4"
|
|
||||||
>
|
|
||||||
Certificate Collections
|
|
||||||
</Button>
|
|
||||||
<div className="mb-4 flex items-center justify-between">
|
|
||||||
<p className="text-3xl font-semibold text-white">{data.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">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Edit}
|
I={ProjectPermissionActions.Edit}
|
||||||
a={ProjectPermissionSub.PkiCollections}
|
a={ProjectPermissionSub.PkiCollections}
|
||||||
@@ -147,7 +129,7 @@ export const PkiCollectionPage = () => {
|
|||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</PageHeader>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="mr-4 w-96">
|
<div className="mr-4 w-96">
|
||||||
<PkiCollectionDetailsSection
|
<PkiCollectionDetailsSection
|
||||||
|
|||||||
@@ -1,9 +1,25 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { PkiCollectionDetailsByIDPage } from "./PkiCollectionDetailsByIDPage";
|
import { PkiCollectionDetailsByIDPage } from "./PkiCollectionDetailsByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId"
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId"
|
||||||
)({
|
)({
|
||||||
component: PkiCollectionDetailsByIDPage
|
component: PkiCollectionDetailsByIDPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Certificate Collections",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/cert-manager/$projectId/overview",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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 { ProjectGeneralTab } from "./components/ProjectGeneralTab";
|
import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
|
||||||
|
|
||||||
@@ -14,12 +14,9 @@ export const SettingsPage = () => {
|
|||||||
<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">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="w-full max-w-7xl px-6">
|
<div className="w-full max-w-7xl">
|
||||||
<div className="my-6">
|
<PageHeader title={t("settings.project.title")} />
|
||||||
<p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p>
|
|
||||||
</div>
|
|
||||||
<Tabs defaultValue={tabs[0].key}>
|
<Tabs defaultValue={tabs[0].key}>
|
||||||
<TabList>
|
<TabList>
|
||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
|
|||||||
@@ -5,5 +5,15 @@ import { SettingsPage } from "./SettingsPage";
|
|||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/settings"
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/settings"
|
||||||
)({
|
)({
|
||||||
component: SettingsPage
|
component: SettingsPage,
|
||||||
|
beforeLoad: ({ context }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Settings"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { workspaceKeys } from "@app/hooks/api";
|
import { workspaceKeys } from "@app/hooks/api";
|
||||||
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||||
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
||||||
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
|
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout"
|
||||||
)({
|
)({
|
||||||
component: () => (
|
component: ProjectLayout,
|
||||||
<OrganizationLayout>
|
|
||||||
<ProjectLayout />
|
|
||||||
</OrganizationLayout>
|
|
||||||
),
|
|
||||||
beforeLoad: async ({ params, context }) => {
|
beforeLoad: async ({ params, context }) => {
|
||||||
await context.queryClient.ensureQueryData({
|
const project = await context.queryClient.ensureQueryData({
|
||||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||||
queryFn: () => fetchWorkspaceById(params.projectId)
|
queryFn: () => fetchWorkspaceById(params.projectId)
|
||||||
});
|
});
|
||||||
@@ -26,5 +21,21 @@ export const Route = createFileRoute(
|
|||||||
}),
|
}),
|
||||||
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "Cert Managers",
|
||||||
|
link: linkOptions({ to: "/organization/cert-manager/overview" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: project.name,
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/cert-manager/$projectId/overview",
|
||||||
|
params: { projectId: project.id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ProjectPermissionCan } from "@app/components/permissions";
|
|||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
|
|
||||||
import { CmekTable } from "./components";
|
import { CmekTable } from "./components";
|
||||||
|
import { PageHeader } from "@app/components/v2";
|
||||||
|
|
||||||
export const OverviewPage = () => {
|
export const OverviewPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -13,15 +14,13 @@ export const OverviewPage = () => {
|
|||||||
<div className="h-full bg-bunker-800">
|
<div className="h-full bg-bunker-800">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{t("common.head-title", { title: "KMS" })}</title>
|
<title>{t("common.head-title", { title: "KMS" })}</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
|
||||||
<meta property="og:image" content="/images/message.png" />
|
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<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">
|
||||||
<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="mr-4 text-3xl font-semibold text-white">Key Management System</p>
|
<PageHeader
|
||||||
<p className="text-md mb-4 text-bunker-300">
|
title="OverviewPage"
|
||||||
Manage keys and perform cryptographic operations.
|
description="Manage keys and perform cryptographic operations."
|
||||||
</p>
|
/>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
passThrough={false}
|
passThrough={false}
|
||||||
renderGuardBanner
|
renderGuardBanner
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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 { ProjectGeneralTab } from "./components/ProjectGeneralTab";
|
import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
|
||||||
|
|
||||||
@@ -14,12 +14,9 @@ export const SettingsPage = () => {
|
|||||||
<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">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="w-full max-w-7xl px-6">
|
<div className="w-full max-w-7xl">
|
||||||
<div className="my-6">
|
<PageHeader title={t("settings.project.title")} />
|
||||||
<p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p>
|
|
||||||
</div>
|
|
||||||
<Tabs defaultValue={tabs[0].key}>
|
<Tabs defaultValue={tabs[0].key}>
|
||||||
<TabList>
|
<TabList>
|
||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
|
|||||||
@@ -5,5 +5,15 @@ import { SettingsPage } from "./SettingsPage";
|
|||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/settings"
|
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/settings"
|
||||||
)({
|
)({
|
||||||
component: SettingsPage
|
component: SettingsPage,
|
||||||
|
beforeLoad: ({ context }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Settings"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { workspaceKeys } from "@app/hooks/api";
|
import { workspaceKeys } from "@app/hooks/api";
|
||||||
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||||
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
|
|
||||||
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout"
|
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout"
|
||||||
)({
|
)({
|
||||||
component: () => (
|
component: ProjectLayout,
|
||||||
<OrganizationLayout>
|
|
||||||
<ProjectLayout />
|
|
||||||
</OrganizationLayout>
|
|
||||||
),
|
|
||||||
beforeLoad: async ({ params, context }) => {
|
beforeLoad: async ({ params, context }) => {
|
||||||
await context.queryClient.ensureQueryData({
|
const project = await context.queryClient.ensureQueryData({
|
||||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||||
queryFn: () => fetchWorkspaceById(params.projectId)
|
queryFn: () => fetchWorkspaceById(params.projectId)
|
||||||
});
|
});
|
||||||
@@ -26,5 +21,21 @@ export const Route = createFileRoute(
|
|||||||
}),
|
}),
|
||||||
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "KMS",
|
||||||
|
link: linkOptions({ to: "/organization/kms/overview" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: project.name,
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/kms/$projectId/overview",
|
||||||
|
params: { projectId: project.id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const Route = createFileRoute(
|
|||||||
link: linkOptions({ to: "/organization/access-management" })
|
link: linkOptions({ to: "/organization/access-management" })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "RBAC"
|
label: "Roles"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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 { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
import { getProjectTitle } from "@app/helpers/project";
|
import { getProjectTitle } from "@app/helpers/project";
|
||||||
import { withProjectPermission } from "@app/hoc";
|
import { withProjectPermission } from "@app/hoc";
|
||||||
@@ -38,11 +38,11 @@ const Page = withProjectPermission(
|
|||||||
|
|
||||||
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">
|
||||||
<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">
|
<PageHeader
|
||||||
{currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"} Access
|
title="Access Control"
|
||||||
Control
|
description="Manage fine-grained access for users, groups, roles, and identities within your project resources."
|
||||||
</p>
|
/>
|
||||||
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
|
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab value={ProjectAccessControlTabs.Member}>Users</Tab>
|
<Tab value={ProjectAccessControlTabs.Member}>Users</Tab>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
import { zodValidator } from "@tanstack/zod-adapter";
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -14,5 +14,21 @@ export const Route = createFileRoute(
|
|||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management"
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/access-management"
|
||||||
)({
|
)({
|
||||||
component: AccessControlPage,
|
component: AccessControlPage,
|
||||||
validateSearch: zodValidator(AccessControlPageQuerySchema)
|
validateSearch: zodValidator(AccessControlPageQuerySchema),
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/cert-manager/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
import { zodValidator } from "@tanstack/zod-adapter";
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -14,5 +14,21 @@ export const Route = createFileRoute(
|
|||||||
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/access-management"
|
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/access-management"
|
||||||
)({
|
)({
|
||||||
component: AccessControlPage,
|
component: AccessControlPage,
|
||||||
validateSearch: zodValidator(AccessControlPageQuerySchema)
|
validateSearch: zodValidator(AccessControlPageQuerySchema),
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/kms/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
import { zodValidator } from "@tanstack/zod-adapter";
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -14,5 +14,21 @@ export const Route = createFileRoute(
|
|||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management"
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/access-management"
|
||||||
)({
|
)({
|
||||||
component: AccessControlPage,
|
component: AccessControlPage,
|
||||||
validateSearch: zodValidator(AccessControlPageQuerySchema)
|
validateSearch: zodValidator(AccessControlPageQuerySchema),
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/secret-manager/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
import { zodValidator } from "@tanstack/zod-adapter";
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -14,5 +14,21 @@ export const Route = createFileRoute(
|
|||||||
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management"
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/access-management"
|
||||||
)({
|
)({
|
||||||
component: AccessControlPage,
|
component: AccessControlPage,
|
||||||
validateSearch: zodValidator(AccessControlPageQuerySchema)
|
validateSearch: zodValidator(AccessControlPageQuerySchema),
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/ssh/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { subject } from "@casl/ability";
|
|||||||
import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
|
import { faChevronLeft } 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 { useNavigate, useParams } from "@tanstack/react-router";
|
||||||
import { format } from "date-fns";
|
import { format, formatRelative } from "date-fns";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, DeleteActionModal, EmptyState, Spinner } from "@app/components/v2";
|
import { Button, DeleteActionModal, EmptyState, PageHeader, Spinner } from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
import { getProjectTitle } from "@app/helpers/project";
|
import { getProjectTitle } from "@app/helpers/project";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
@@ -82,78 +82,37 @@ const Page = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 p-6 text-white">
|
<div className="container mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 text-white">
|
||||||
<div className="mb-4">
|
|
||||||
<Button
|
|
||||||
variant="link"
|
|
||||||
type="submit"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
|
||||||
onClick={() => {
|
|
||||||
navigate({
|
|
||||||
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
|
||||||
params: {
|
|
||||||
projectId: workspaceId
|
|
||||||
},
|
|
||||||
search: {
|
|
||||||
selectedTab: "identities"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="mb-4"
|
|
||||||
>
|
|
||||||
{currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"} Access
|
|
||||||
Control
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{identityMembershipDetails ? (
|
{identityMembershipDetails ? (
|
||||||
<>
|
<>
|
||||||
<div className="mb-4">
|
<PageHeader
|
||||||
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
title={identityMembershipDetails?.identity?.name}
|
||||||
<div className="mb-4 flex items-center justify-between">
|
description={`Identity joined on ${identityMembershipDetails?.createdAt && formatRelative(new Date(identityMembershipDetails?.createdAt || ""), new Date())}`}
|
||||||
<h3 className="text-xl font-semibold text-mineshaft-100">
|
>
|
||||||
Project Identity Access
|
<div>
|
||||||
</h3>
|
<ProjectPermissionCan
|
||||||
<div>
|
I={ProjectPermissionActions.Delete}
|
||||||
<ProjectPermissionCan
|
a={subject(ProjectPermissionSub.Identity, {
|
||||||
I={ProjectPermissionActions.Delete}
|
identityId: identityMembershipDetails?.identity?.id
|
||||||
a={subject(ProjectPermissionSub.Identity, {
|
})}
|
||||||
identityId: identityMembershipDetails?.identity?.id
|
renderTooltip
|
||||||
})}
|
allowedLabel="Remove from project"
|
||||||
renderTooltip
|
>
|
||||||
allowedLabel="Remove from project"
|
{(isAllowed) => (
|
||||||
|
<Button
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="outline_bg"
|
||||||
|
size="xs"
|
||||||
|
isDisabled={!isAllowed}
|
||||||
|
isLoading={isDeletingIdentity}
|
||||||
|
onClick={() => handlePopUpOpen("deleteIdentity")}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
Remove Identity
|
||||||
<Button
|
</Button>
|
||||||
colorSchema="danger"
|
)}
|
||||||
variant="outline_bg"
|
</ProjectPermissionCan>
|
||||||
size="xs"
|
|
||||||
isDisabled={!isAllowed}
|
|
||||||
isLoading={isDeletingIdentity}
|
|
||||||
onClick={() => handlePopUpOpen("deleteIdentity")}
|
|
||||||
>
|
|
||||||
Remove Identity
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</ProjectPermissionCan>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-12">
|
|
||||||
<div>
|
|
||||||
<span className="text-xs font-semibold text-gray-400">Name</span>
|
|
||||||
{identityMembershipDetails && (
|
|
||||||
<p className="text-lg capitalize">
|
|
||||||
{identityMembershipDetails?.identity?.name}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4 text-sm text-gray-400">
|
|
||||||
Joined on{" "}
|
|
||||||
{identityMembershipDetails?.createdAt &&
|
|
||||||
format(new Date(identityMembershipDetails?.createdAt || ""), "yyyy-MM-dd")}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PageHeader>
|
||||||
<IdentityRoleDetailsSection
|
<IdentityRoleDetailsSection
|
||||||
identityMembershipDetails={identityMembershipDetails}
|
identityMembershipDetails={identityMembershipDetails}
|
||||||
isMembershipDetailsLoading={isMembershipDetailsLoading}
|
isMembershipDetailsLoading={isMembershipDetailsLoading}
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
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/cert-manager/$projectId/_cert-manager-layout/identities/$identityId"
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/identities/$identityId"
|
||||||
)({
|
)({
|
||||||
component: IdentityDetailsByIDPage
|
component: IdentityDetailsByIDPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/cert-manager/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Identities"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
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/kms/$projectId/_kms-layout/identities/$identityId"
|
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/identities/$identityId"
|
||||||
)({
|
)({
|
||||||
component: IdentityDetailsByIDPage
|
component: IdentityDetailsByIDPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/kms/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Identities"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
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/secret-manager/$projectId/_secret-manager-layout/identities/$identityId"
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/identities/$identityId"
|
||||||
)({
|
)({
|
||||||
component: IdentityDetailsByIDPage
|
component: IdentityDetailsByIDPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/secret-manager/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Identities"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
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/ssh/$projectId/_ssh-layout/identities/$identityId"
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/identities/$identityId"
|
||||||
)({
|
)({
|
||||||
component: IdentityDetailsByIDPage
|
component: IdentityDetailsByIDPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/ssh/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Identities"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { faChevronLeft } 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 { format } from "date-fns";
|
import { formatRelative } from "date-fns";
|
||||||
|
|
||||||
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";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, DeleteActionModal, EmptyState, Spinner } from "@app/components/v2";
|
import { Button, DeleteActionModal, EmptyState, PageHeader, Spinner } from "@app/components/v2";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
useOrganization,
|
useOrganization,
|
||||||
useWorkspace
|
useWorkspace
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { getProjectTitle } from "@app/helpers/project";
|
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useDeleteUserFromWorkspace, useGetWorkspaceUserDetails } from "@app/hooks/api";
|
import { useDeleteUserFromWorkspace, useGetWorkspaceUserDetails } from "@app/hooks/api";
|
||||||
|
|
||||||
@@ -83,76 +80,36 @@ export const Page = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 p-6 text-white">
|
<div className="container mx-auto flex max-w-7xl flex-col justify-between bg-bunker-800 p-6 text-white">
|
||||||
<div className="mb-4">
|
|
||||||
<Button
|
|
||||||
variant="link"
|
|
||||||
type="submit"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
|
||||||
onClick={() => {
|
|
||||||
navigate({
|
|
||||||
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
|
||||||
params: {
|
|
||||||
projectId: currentWorkspace.id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="mb-4"
|
|
||||||
>
|
|
||||||
{currentWorkspace?.type ? getProjectTitle(currentWorkspace?.type) : "Project"} Access
|
|
||||||
Control
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{membershipDetails ? (
|
{membershipDetails ? (
|
||||||
<>
|
<>
|
||||||
<div className="mb-4">
|
<PageHeader
|
||||||
<div className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
title={
|
||||||
<div className="mb-4 flex items-center justify-between">
|
membershipDetails.user.firstName || membershipDetails.user.lastName
|
||||||
<h3 className="text-xl font-semibold text-mineshaft-100">Project User Access</h3>
|
? `${membershipDetails.user.firstName} ${membershipDetails.user.lastName}`
|
||||||
<div>
|
: "-"
|
||||||
<ProjectPermissionCan
|
}
|
||||||
I={ProjectPermissionActions.Delete}
|
description={`User joined on ${membershipDetails?.createdAt && formatRelative(new Date(membershipDetails?.createdAt || ""), new Date())}`}
|
||||||
a={ProjectPermissionSub.Member}
|
>
|
||||||
renderTooltip
|
<ProjectPermissionCan
|
||||||
allowedLabel="Remove from project"
|
I={ProjectPermissionActions.Delete}
|
||||||
>
|
a={ProjectPermissionSub.Member}
|
||||||
{(isAllowed) => (
|
renderTooltip
|
||||||
<Button
|
allowedLabel="Remove from project"
|
||||||
colorSchema="danger"
|
>
|
||||||
variant="outline_bg"
|
{(isAllowed) => (
|
||||||
size="xs"
|
<Button
|
||||||
isDisabled={!isAllowed}
|
colorSchema="danger"
|
||||||
isLoading={isRemovingUserFromWorkspace}
|
variant="outline_bg"
|
||||||
onClick={() => handlePopUpOpen("removeMember")}
|
size="xs"
|
||||||
>
|
isDisabled={!isAllowed}
|
||||||
Remove User
|
isLoading={isRemovingUserFromWorkspace}
|
||||||
</Button>
|
onClick={() => handlePopUpOpen("removeMember")}
|
||||||
)}
|
>
|
||||||
</ProjectPermissionCan>
|
Remove User
|
||||||
</div>
|
</Button>
|
||||||
</div>
|
)}
|
||||||
<div className="flex gap-12">
|
</ProjectPermissionCan>
|
||||||
<div>
|
</PageHeader>
|
||||||
<span className="text-xs font-semibold text-gray-400">Name</span>
|
|
||||||
{membershipDetails && (
|
|
||||||
<p className="text-lg capitalize">
|
|
||||||
{membershipDetails.user.firstName || membershipDetails.user.lastName
|
|
||||||
? `${membershipDetails.user.firstName} ${membershipDetails.user.lastName}`
|
|
||||||
: "-"}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-xs font-semibold text-gray-400">Email</span>
|
|
||||||
{membershipDetails && <p className="text-lg">{membershipDetails?.user?.email}</p>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4 text-sm text-gray-400">
|
|
||||||
Joined on{" "}
|
|
||||||
{membershipDetails?.createdAt &&
|
|
||||||
format(new Date(membershipDetails?.createdAt || ""), "yyyy-MM-dd")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<MemberRoleDetailsSection
|
<MemberRoleDetailsSection
|
||||||
membershipDetails={membershipDetails}
|
membershipDetails={membershipDetails}
|
||||||
isMembershipDetailsLoading={isMembershipDetailsLoading}
|
isMembershipDetailsLoading={isMembershipDetailsLoading}
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
|
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/members/$membershipId"
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/members/$membershipId"
|
||||||
)({
|
)({
|
||||||
component: MemberDetailsByIDPage
|
component: MemberDetailsByIDPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/cert-manager/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Users"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
|
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/members/$membershipId"
|
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/members/$membershipId"
|
||||||
)({
|
)({
|
||||||
component: MemberDetailsByIDPage
|
component: MemberDetailsByIDPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/kms/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Users"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
|
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/members/$membershipId"
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/members/$membershipId"
|
||||||
)({
|
)({
|
||||||
component: MemberDetailsByIDPage
|
component: MemberDetailsByIDPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/secret-manager/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Users"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
|
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/members/$membershipId"
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/members/$membershipId"
|
||||||
)({
|
)({
|
||||||
component: MemberDetailsByIDPage
|
component: MemberDetailsByIDPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/ssh/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Users"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
@@ -83,38 +82,18 @@ 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: `/${currentWorkspace?.type}/$projectId/access-management` as const,
|
|
||||||
params: {
|
|
||||||
projectId
|
|
||||||
},
|
|
||||||
search: {
|
|
||||||
selectedTab: ProjectAccessControlTabs.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>
|
||||||
<DropdownMenuContent align="start" className="p-1">
|
<DropdownMenuContent align="end" className="p-1">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Edit}
|
I={ProjectPermissionActions.Edit}
|
||||||
a={ProjectPermissionSub.Role}
|
a={ProjectPermissionSub.Role}
|
||||||
@@ -156,7 +135,7 @@ 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 roleSlug={roleSlug} handlePopUpOpen={handlePopUpOpen} />
|
<RoleDetailsSection roleSlug={roleSlug} handlePopUpOpen={handlePopUpOpen} />
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
|
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/roles/$roleSlug"
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/roles/$roleSlug"
|
||||||
)({
|
)({
|
||||||
component: RoleDetailsBySlugPage
|
component: RoleDetailsBySlugPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/cert-manager/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Roles"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
|
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/roles/$roleSlug"
|
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/roles/$roleSlug"
|
||||||
)({
|
)({
|
||||||
component: RoleDetailsBySlugPage
|
component: RoleDetailsBySlugPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/kms/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Roles"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
|
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/roles/$roleSlug"
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/roles/$roleSlug"
|
||||||
)({
|
)({
|
||||||
component: RoleDetailsBySlugPage
|
component: RoleDetailsBySlugPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/secret-manager/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Roles"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
|
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/roles/$roleSlug"
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/roles/$roleSlug"
|
||||||
)({
|
)({
|
||||||
component: RoleDetailsBySlugPage
|
component: RoleDetailsBySlugPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Access Control",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/ssh/$projectId/access-management",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Roles"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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 { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
|
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
@@ -42,12 +42,9 @@ export const SettingsPage = () => {
|
|||||||
<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">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
<title>{t("common.head-title", { title: t("settings.project.title") })}</title>
|
||||||
<link rel="icon" href="/infisical.ico" />
|
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="w-full max-w-7xl px-6">
|
<div className="w-full max-w-7xl">
|
||||||
<div className="my-6">
|
<PageHeader title={t("settings.project.title")} />
|
||||||
<p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p>
|
|
||||||
</div>
|
|
||||||
<Tabs defaultValue={tabs[0].key}>
|
<Tabs defaultValue={tabs[0].key}>
|
||||||
<TabList>
|
<TabList>
|
||||||
{tabs
|
{tabs
|
||||||
|
|||||||
@@ -5,5 +5,15 @@ import { SettingsPage } from "./SettingsPage";
|
|||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/settings"
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/settings"
|
||||||
)({
|
)({
|
||||||
component: SettingsPage
|
component: SettingsPage,
|
||||||
|
beforeLoad: ({ context }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Settings"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { workspaceKeys } from "@app/hooks/api";
|
import { workspaceKeys } from "@app/hooks/api";
|
||||||
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||||
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
||||||
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
|
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout"
|
||||||
)({
|
)({
|
||||||
component: () => (
|
component: ProjectLayout,
|
||||||
<OrganizationLayout>
|
|
||||||
<ProjectLayout />
|
|
||||||
</OrganizationLayout>
|
|
||||||
),
|
|
||||||
beforeLoad: async ({ params, context }) => {
|
beforeLoad: async ({ params, context }) => {
|
||||||
await context.queryClient.ensureQueryData({
|
const project = await context.queryClient.ensureQueryData({
|
||||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||||
queryFn: () => fetchWorkspaceById(params.projectId)
|
queryFn: () => fetchWorkspaceById(params.projectId)
|
||||||
});
|
});
|
||||||
@@ -26,5 +21,21 @@ export const Route = createFileRoute(
|
|||||||
}),
|
}),
|
||||||
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "Secret Managers",
|
||||||
|
link: linkOptions({ to: "/organization/secret-manager/overview" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: project.name,
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/secret-manager/$projectId/overview",
|
||||||
|
params: { projectId: project.id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||||
|
|
||||||
import { SshCaSection, SshCertificatesSection } from "./components";
|
import { SshCaSection, SshCertificatesSection } from "./components";
|
||||||
@@ -19,12 +19,14 @@ export const OverviewPage = () => {
|
|||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{t("common.head-title", { title: "Certificates" })}</title>
|
<title>{t("common.head-title", { title: "Certificates" })}</title>
|
||||||
<meta property="og:image" content="/images/message.png" />
|
|
||||||
</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 bg-bunker-800 text-white">
|
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||||
<div className="mx-auto mb-6 w-full max-w-7xl 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">SSH</p>
|
<PageHeader
|
||||||
|
title="Overview"
|
||||||
|
description="Infisical SSH lets you issue SSH credentials to clients to provide short-lived, secure SSH access to infrastructure."
|
||||||
|
/>
|
||||||
<Tabs defaultValue={TabSections.SshCertificates}>
|
<Tabs defaultValue={TabSections.SshCertificates}>
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab value={TabSections.SshCertificates}>SSH Certificates</Tab>
|
<Tab value={TabSections.SshCertificates}>SSH Certificates</Tab>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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 { ProjectGeneralTab } from "./components/ProjectGeneralTab";
|
import { ProjectGeneralTab } from "./components/ProjectGeneralTab";
|
||||||
|
|
||||||
@@ -15,10 +15,8 @@ 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 px-6">
|
<div className="w-full max-w-7xl">
|
||||||
<div className="my-6">
|
<PageHeader title={t("settings.project.title")} />
|
||||||
<p className="text-3xl font-semibold text-gray-200">{t("settings.project.title")}</p>
|
|
||||||
</div>
|
|
||||||
<Tabs defaultValue={tabs[0].key}>
|
<Tabs defaultValue={tabs[0].key}>
|
||||||
<TabList>
|
<TabList>
|
||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
|
|||||||
@@ -5,5 +5,15 @@ import { SettingsPage } from "./SettingsPage";
|
|||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/settings"
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/settings"
|
||||||
)({
|
)({
|
||||||
component: SettingsPage
|
component: SettingsPage,
|
||||||
|
beforeLoad: ({ context }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "Settings"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
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";
|
||||||
|
|
||||||
@@ -13,6 +11,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";
|
||||||
@@ -71,34 +70,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.friendlyName}>
|
||||||
variant="link"
|
|
||||||
type="submit"
|
|
||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
|
||||||
onClick={() =>
|
|
||||||
navigate({
|
|
||||||
to: `/${ProjectType.SSH}/$projectId/overview`,
|
|
||||||
params: {
|
|
||||||
projectId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="mb-4"
|
|
||||||
>
|
|
||||||
SSH Certificate Authorities
|
|
||||||
</Button>
|
|
||||||
<div className="mb-4 flex items-center justify-between">
|
|
||||||
<p className="text-3xl font-semibold text-white">{data.friendlyName}</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">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Delete}
|
I={ProjectPermissionActions.Delete}
|
||||||
a={ProjectPermissionSub.SshCertificateAuthorities}
|
a={ProjectPermissionSub.SshCertificateAuthorities}
|
||||||
@@ -123,7 +105,7 @@ const Page = () => {
|
|||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</PageHeader>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="mr-4 w-96">
|
<div className="mr-4 w-96">
|
||||||
<SshCaDetailsSection caId={caId} handlePopUpOpen={handlePopUpOpen} />
|
<SshCaDetailsSection caId={caId} handlePopUpOpen={handlePopUpOpen} />
|
||||||
|
|||||||
@@ -1,9 +1,25 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { SshCaByIDPage } from "./SshCaByIDPage";
|
import { SshCaByIDPage } from "./SshCaByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId"
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout/ca/$caId"
|
||||||
)({
|
)({
|
||||||
component: SshCaByIDPage
|
component: SshCaByIDPage,
|
||||||
|
beforeLoad: ({ context, params }) => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
...context.breadcrumbs,
|
||||||
|
{
|
||||||
|
label: "SSH Certificate Authorities",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/ssh/$projectId/overview",
|
||||||
|
params: {
|
||||||
|
projectId: params.projectId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { workspaceKeys } from "@app/hooks/api";
|
import { workspaceKeys } from "@app/hooks/api";
|
||||||
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||||
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
||||||
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
|
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
"/_authenticate/_inject-org-details/ssh/$projectId/_ssh-layout"
|
||||||
)({
|
)({
|
||||||
component: () => (
|
component: ProjectLayout,
|
||||||
<OrganizationLayout>
|
|
||||||
<ProjectLayout />
|
|
||||||
</OrganizationLayout>
|
|
||||||
),
|
|
||||||
beforeLoad: async ({ params, context }) => {
|
beforeLoad: async ({ params, context }) => {
|
||||||
await context.queryClient.ensureQueryData({
|
const project = await context.queryClient.ensureQueryData({
|
||||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||||
queryFn: () => fetchWorkspaceById(params.projectId)
|
queryFn: () => fetchWorkspaceById(params.projectId)
|
||||||
});
|
});
|
||||||
@@ -26,5 +21,21 @@ export const Route = createFileRoute(
|
|||||||
}),
|
}),
|
||||||
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "SSH",
|
||||||
|
link: linkOptions({ to: "/organization/ssh/overview" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: project.name,
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/ssh/$projectId/overview",
|
||||||
|
params: { projectId: project.id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user