mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 07:27:43 +00:00
feat: resolved almost all ts issues
This commit is contained in:
@@ -4,6 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { Link, useParams } from "@tanstack/react-router";
|
import { Link, useParams } from "@tanstack/react-router";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
import { useOrganization, useWorkspace } from "@app/context";
|
import { useOrganization, useWorkspace } from "@app/context";
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
@@ -59,7 +60,7 @@ export default function NavHeader({
|
|||||||
const [isHoveringCopyButton, setIsHoveringCopyButton] = useState(false);
|
const [isHoveringCopyButton, setIsHoveringCopyButton] = useState(false);
|
||||||
|
|
||||||
const routerEnvSlug = useParams({
|
const routerEnvSlug = useParams({
|
||||||
from: "/_authenticate/_ctx-org-details/secret-manager/$projectId/_layout-secret-manager/secrets/$envSlug/",
|
from: ROUTE_PATHS.SecretDashboardPage.id,
|
||||||
select: (el) => el.envSlug
|
select: (el) => el.envSlug
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export { hasSecretReference,SecretReferenceTree } from "./SecretReferenceDetails";
|
export { hasSecretReference, SecretReferenceTree } from "./SecretReferenceDetails";
|
||||||
|
|||||||
@@ -6,12 +6,36 @@ const setRoute = <TFull extends FileRouteTypes["fullPaths"], TId extends FileRou
|
|||||||
) => ({ full, id }) as const;
|
) => ({ full, id }) as const;
|
||||||
|
|
||||||
export const ROUTE_PATHS = Object.freeze({
|
export const ROUTE_PATHS = Object.freeze({
|
||||||
|
Auth: {
|
||||||
|
LoginSSO: setRoute("/login/sso", "/_restrict-login-signup/login/sso")
|
||||||
|
},
|
||||||
|
Organization: {
|
||||||
|
SecretScanning: setRoute(
|
||||||
|
"/organization/secret-scanning",
|
||||||
|
"/_authenticate/_inject-org-details/organization/_layout/secret-scanning"
|
||||||
|
),
|
||||||
|
SettingsPage: setRoute(
|
||||||
|
"/organization/settings",
|
||||||
|
"/_authenticate/_inject-org-details/organization/_layout/settings"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
SecretManager: {
|
||||||
|
ApprovalPage: setRoute(
|
||||||
|
"/secret-manager/$projectId/approval",
|
||||||
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/approval"
|
||||||
|
),
|
||||||
|
SecretDashboardPage: setRoute(
|
||||||
|
"/secret-manager/$projectId/secrets/$envSlug",
|
||||||
|
"/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/secrets/$envSlug"
|
||||||
|
)
|
||||||
|
},
|
||||||
ProviderSuccessPage: setRoute(
|
ProviderSuccessPage: setRoute(
|
||||||
"/login/provider/success",
|
"/login/provider/success",
|
||||||
"/_restrict-login-signup/login/provider/success"
|
"/_restrict-login-signup/login/provider/success"
|
||||||
),
|
),
|
||||||
SignUpSsoPage: setRoute("/signup/sso", "/_restrict-login-signup/signup/sso"),
|
SignUpSsoPage: setRoute("/signup/sso", "/_restrict-login-signup/signup/sso"),
|
||||||
PasswordResetPage: setRoute("/password-reset", "/_restrict-login-signup/password-reset"),
|
PasswordResetPage: setRoute("/password-reset", "/_restrict-login-signup/password-reset"),
|
||||||
|
ViewSharedSecretByIDPage: setRoute("/shared/secret/$secretId", "/shared/secret/$secretId"),
|
||||||
OrgGroupDetailsByIDPage: setRoute(
|
OrgGroupDetailsByIDPage: setRoute(
|
||||||
"/organization/groups/$groupId",
|
"/organization/groups/$groupId",
|
||||||
"/_authenticate/_inject-org-details/organization/_layout/groups/$groupId"
|
"/_authenticate/_inject-org-details/organization/_layout/groups/$groupId"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { OrgPermissionSet } from "./types";
|
|||||||
|
|
||||||
export const useOrgPermission = () => {
|
export const useOrgPermission = () => {
|
||||||
const organizationId = useRouteContext({
|
const organizationId = useRouteContext({
|
||||||
from: "/_authenticate/_ctx-org-details",
|
from: "/_authenticate/_inject-org-details",
|
||||||
select: (el) => el.organizationId
|
select: (el) => el.organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { fetchOrganizationById, organizationKeys } from "@app/hooks/api/organiza
|
|||||||
|
|
||||||
export const useOrganization = () => {
|
export const useOrganization = () => {
|
||||||
const organizationId = useRouteContext({
|
const organizationId = useRouteContext({
|
||||||
from: "/_authenticate/_ctx-org-details",
|
from: "/_authenticate/_inject-org-details",
|
||||||
select: (el) => el.organizationId
|
select: (el) => el.organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { fetchOrgSubscription, subscriptionQueryKeys } from "@app/hooks/api/subs
|
|||||||
|
|
||||||
export const useSubscription = () => {
|
export const useSubscription = () => {
|
||||||
const organizationId = useRouteContext({
|
const organizationId = useRouteContext({
|
||||||
from: "/_authenticate/_ctx-org-details",
|
from: "/_authenticate/_inject-org-details",
|
||||||
select: (el) => el.organizationId
|
select: (el) => el.organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { faArrowLeft, faInfo, faMobile, faQuestion } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { Link, Outlet } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { WishForm } from "@app/components/features/WishForm";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||||
|
|
||||||
|
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
|
||||||
|
import { INFISICAL_SUPPORT_OPTIONS } from "../OrganizationLayout/components/SidebarFooter/SidebarFooter";
|
||||||
|
|
||||||
|
export const AdminLayout = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const infisicalPlatformVersion = import.meta.env.VITE_INFISICAL_PLATFORM_VERSION;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex">
|
||||||
|
{!window.isSecureContext && <InsecureConnectionBanner />}
|
||||||
|
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
|
||||||
|
<aside className="dark w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60">
|
||||||
|
<nav className="items-between flex h-full flex-col justify-between overflow-y-auto dark:[color-scheme:dark]">
|
||||||
|
<div className="flex-grow">
|
||||||
|
<Link to={`/organization/${ProjectType.SecretManager}/overview` as const}>
|
||||||
|
<div className="my-6 flex cursor-default items-center justify-center pr-2 text-sm text-mineshaft-300 hover:text-mineshaft-100">
|
||||||
|
<FontAwesomeIcon icon={faArrowLeft} className="pr-3" />
|
||||||
|
Back to organization
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="relative mt-10 flex w-full cursor-default flex-col items-center px-3 text-sm text-mineshaft-400">
|
||||||
|
{(window.location.origin.includes("https://app.infisical.com") ||
|
||||||
|
window.location.origin.includes("https://gamma.infisical.com")) && <WishForm />}
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
|
||||||
|
<FontAwesomeIcon icon={faQuestion} className="mr-3 px-[0.1rem]" />
|
||||||
|
Help & Support
|
||||||
|
</div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
|
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => (
|
||||||
|
<DropdownMenuItem key={url as string}>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href={String(url)}
|
||||||
|
className="flex w-full items-center rounded-md font-normal text-mineshaft-300 duration-200"
|
||||||
|
>
|
||||||
|
<div className="relative flex w-full cursor-pointer select-none items-center justify-start rounded-md">
|
||||||
|
{icon}
|
||||||
|
<div className="text-sm">{text}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
{infisicalPlatformVersion && (
|
||||||
|
<div className="mb-2 mt-2 w-full cursor-default pl-5 text-sm duration-200 hover:text-mineshaft-200">
|
||||||
|
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
|
||||||
|
Version: {infisicalPlatformVersion}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="z-[200] flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden">
|
||||||
|
<FontAwesomeIcon icon={faMobile} className="mb-8 text-7xl text-gray-300" />
|
||||||
|
<p className="max-w-sm px-6 text-center text-lg text-gray-200">
|
||||||
|
{` ${t("common.no-mobile")} `}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { AdminLayout } from "./AdminLayout";
|
||||||
@@ -92,7 +92,7 @@ export const OrganizationLayout = () => {
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/organization/members">
|
<Link to="/organization/access-management">
|
||||||
{({ isActive }) => (
|
{({ isActive }) => (
|
||||||
<MenuItem isSelected={isActive} icon="system-outline-96-groups">
|
<MenuItem isSelected={isActive} icon="system-outline-96-groups">
|
||||||
Access Control
|
Access Control
|
||||||
|
|||||||
+1
-1
@@ -62,7 +62,7 @@ export const SidebarFooter = () => {
|
|||||||
{(window.location.origin.includes("https://app.infisical.com") ||
|
{(window.location.origin.includes("https://app.infisical.com") ||
|
||||||
window.location.origin.includes("https://gamma.infisical.com")) && <WishForm />}
|
window.location.origin.includes("https://gamma.infisical.com")) && <WishForm />}
|
||||||
<Link
|
<Link
|
||||||
to="/organization/members"
|
to="/organization/access-management"
|
||||||
search={{
|
search={{
|
||||||
action: "invite"
|
action: "invite"
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export const ProjectLayout = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
<Link
|
<Link
|
||||||
to={`/${currentWorkspace.type}/$projectId/access` as const}
|
to={`/${currentWorkspace.type}/$projectId/access-management` as const}
|
||||||
params={{
|
params={{
|
||||||
projectId: currentWorkspace.id
|
projectId: currentWorkspace.id
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const SidebarHeader = ({ onChangeOrg }: Prop) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-12 cursor-default items-center px-3 pt-6">
|
<div className="flex h-12 cursor-default items-center px-3 pt-6">
|
||||||
<Link to={`/organization/$${currentWorkspace.type}/overview` as const}>
|
<Link to={`/organization/${currentWorkspace.type}/overview` as const}>
|
||||||
<div className="pl-1 pr-2 text-mineshaft-400 duration-200 hover:text-mineshaft-100">
|
<div className="pl-1 pr-2 text-mineshaft-400 duration-200 hover:text-mineshaft-100">
|
||||||
<FontAwesomeIcon icon={faArrowLeft} />
|
<FontAwesomeIcon icon={faArrowLeft} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,357 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { faAt } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ContentLoader,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
SelectClear,
|
||||||
|
SelectItem,
|
||||||
|
Switch,
|
||||||
|
Tab,
|
||||||
|
TabList,
|
||||||
|
TabPanel,
|
||||||
|
Tabs
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useServerConfig, useUser } from "@app/context";
|
||||||
|
import {
|
||||||
|
useGetOrganizations,
|
||||||
|
useGetServerRootKmsEncryptionDetails,
|
||||||
|
useUpdateServerConfig
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
|
||||||
|
import { AuthPanel } from "./components/AuthPanel";
|
||||||
|
import { EncryptionPanel } from "./components/EncryptionPanel";
|
||||||
|
import { IntegrationPanel } from "./components/IntegrationPanel";
|
||||||
|
import { RateLimitPanel } from "./components/RateLimitPanel";
|
||||||
|
import { UserPanel } from "./components/UserPanel";
|
||||||
|
|
||||||
|
enum TabSections {
|
||||||
|
Settings = "settings",
|
||||||
|
Encryption = "encryption",
|
||||||
|
Auth = "auth",
|
||||||
|
RateLimit = "rate-limit",
|
||||||
|
Integrations = "integrations",
|
||||||
|
Users = "users"
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SignUpModes {
|
||||||
|
Disabled = "disabled",
|
||||||
|
Anyone = "anyone"
|
||||||
|
}
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
signUpMode: z.nativeEnum(SignUpModes),
|
||||||
|
allowedSignUpDomain: z.string().optional().nullable(),
|
||||||
|
trustSamlEmails: z.boolean(),
|
||||||
|
trustLdapEmails: z.boolean(),
|
||||||
|
trustOidcEmails: z.boolean(),
|
||||||
|
defaultAuthOrgId: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
type TDashboardForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const OverviewPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const data = useServerConfig();
|
||||||
|
const { data: serverRootKmsDetails } = useGetServerRootKmsEncryptionDetails();
|
||||||
|
const { config } = data;
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
formState: { isSubmitting, isDirty }
|
||||||
|
} = useForm<TDashboardForm>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
values: {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
signUpMode: config.allowSignUp ? SignUpModes.Anyone : SignUpModes.Disabled,
|
||||||
|
allowedSignUpDomain: config.allowedSignUpDomain,
|
||||||
|
trustSamlEmails: config.trustSamlEmails,
|
||||||
|
trustLdapEmails: config.trustLdapEmails,
|
||||||
|
trustOidcEmails: config.trustOidcEmails,
|
||||||
|
defaultAuthOrgId: config.defaultAuthOrgId ?? ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const signUpMode = watch("signUpMode");
|
||||||
|
const defaultAuthOrgId = watch("defaultAuthOrgId");
|
||||||
|
|
||||||
|
const { user } = useUser();
|
||||||
|
const { mutateAsync: updateServerConfig } = useUpdateServerConfig();
|
||||||
|
|
||||||
|
const organizations = useGetOrganizations();
|
||||||
|
|
||||||
|
const isNotAllowed = !user?.superAdmin;
|
||||||
|
const onFormSubmit = async (formData: TDashboardForm) => {
|
||||||
|
try {
|
||||||
|
const { allowedSignUpDomain, trustSamlEmails, trustLdapEmails, trustOidcEmails } = formData;
|
||||||
|
|
||||||
|
await updateServerConfig({
|
||||||
|
defaultAuthOrgId: defaultAuthOrgId || null,
|
||||||
|
allowSignUp: signUpMode !== SignUpModes.Disabled,
|
||||||
|
allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : null,
|
||||||
|
trustSamlEmails,
|
||||||
|
trustLdapEmails,
|
||||||
|
trustOidcEmails
|
||||||
|
});
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully changed sign up setting.",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to update sign up setting."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>{t("common.head-title", { title: t("admin.dashboard") })}</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
<meta property="og:image" content="/images/message.png" />
|
||||||
|
<meta property="og:title" content={t("admin.dashboard.og-title") ?? ""} />
|
||||||
|
<meta name="og:description" content={t("admin.dashboard.og-description") ?? ""} />
|
||||||
|
</Helmet>
|
||||||
|
<div className="h-full">
|
||||||
|
<div className="container mx-auto max-w-7xl px-4 pb-12 text-white dark:[color-scheme:dark]">
|
||||||
|
<div className="mx-auto mb-6 w-full max-w-7xl pt-6">
|
||||||
|
<div className="mb-8 flex flex-col items-start justify-between text-xl">
|
||||||
|
<h1 className="text-3xl font-semibold">Server Admin Console</h1>
|
||||||
|
<p className="text-base text-bunker-300">
|
||||||
|
Manage your instance level configurations.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isNotAllowed ? (
|
||||||
|
<ContentLoader text={isNotAllowed ? "Redirecting to org page..." : undefined} />
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<Tabs defaultValue={TabSections.Settings}>
|
||||||
|
<TabList>
|
||||||
|
<div className="flex w-full flex-row border-b border-mineshaft-600">
|
||||||
|
<Tab value={TabSections.Settings}>General</Tab>
|
||||||
|
<Tab value={TabSections.Encryption}>Encryption</Tab>
|
||||||
|
<Tab value={TabSections.Auth}>Authentication</Tab>
|
||||||
|
<Tab value={TabSections.RateLimit}>Rate Limit</Tab>
|
||||||
|
<Tab value={TabSections.Integrations}>Integrations</Tab>
|
||||||
|
<Tab value={TabSections.Users}>Users</Tab>
|
||||||
|
</div>
|
||||||
|
</TabList>
|
||||||
|
<TabPanel value={TabSections.Settings}>
|
||||||
|
<form
|
||||||
|
className="mb-6 space-y-8 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||||
|
onSubmit={handleSubmit(onFormSubmit)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col justify-start">
|
||||||
|
<div className="mb-2 text-xl font-semibold text-mineshaft-100">
|
||||||
|
Allow user signups
|
||||||
|
</div>
|
||||||
|
<div className="mb-4 max-w-sm text-sm text-mineshaft-400">
|
||||||
|
Select if you want users to be able to signup freely into your Infisical
|
||||||
|
instance.
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="signUpMode"
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
className="max-w-sm"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
className="w-full bg-mineshaft-700"
|
||||||
|
dropdownContainerClassName="bg-mineshaft-800"
|
||||||
|
defaultValue={field.value}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
{...field}
|
||||||
|
>
|
||||||
|
<SelectItem value={SignUpModes.Disabled}>Disabled</SelectItem>
|
||||||
|
<SelectItem value={SignUpModes.Anyone}>Anyone</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{signUpMode === "anyone" && (
|
||||||
|
<div className="flex flex-col justify-start">
|
||||||
|
<div className="mb-4 text-xl font-semibold text-mineshaft-100">
|
||||||
|
Restrict signup by email domain(s)
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue=""
|
||||||
|
name="allowedSignUpDomain"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Leave blank to allow any email domains"
|
||||||
|
className="w-72"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
value={field.value || ""}
|
||||||
|
placeholder="gmail.com, aws.com, redhat.com"
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faAt} />}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-col justify-start">
|
||||||
|
<div className="mb-2 text-xl font-semibold text-mineshaft-100">
|
||||||
|
Default organization
|
||||||
|
</div>
|
||||||
|
<div className="mb-4 max-w-sm text-sm text-mineshaft-400">
|
||||||
|
Select the default organization you want to set for SAML/LDAP/OIDC based
|
||||||
|
logins. When selected, user logins will be automatically scoped to the
|
||||||
|
selected organization.
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="defaultAuthOrgId"
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
className="max-w-sm"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder="Allow all organizations"
|
||||||
|
className="w-full bg-mineshaft-700"
|
||||||
|
dropdownContainerClassName="bg-mineshaft-800"
|
||||||
|
defaultValue={field.value ?? " "}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
{...field}
|
||||||
|
>
|
||||||
|
<SelectClear
|
||||||
|
selectValue={defaultAuthOrgId}
|
||||||
|
onClear={() => {
|
||||||
|
console.log("clearing");
|
||||||
|
onChange("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Allow all organizations
|
||||||
|
</SelectClear>
|
||||||
|
{organizations.data?.map((org) => (
|
||||||
|
<SelectItem key={org.id} value={org.id}>
|
||||||
|
{org.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col justify-start">
|
||||||
|
<div className="mb-2 text-xl font-semibold text-mineshaft-100">
|
||||||
|
Trust emails
|
||||||
|
</div>
|
||||||
|
<div className="mb-4 max-w-sm text-sm text-mineshaft-400">
|
||||||
|
Select if you want Infisical to trust external emails from SAML/LDAP/OIDC
|
||||||
|
identity providers. If set to false, then Infisical will prompt
|
||||||
|
SAML/LDAP/OIDC provisioned users to verify their email upon their first
|
||||||
|
login.
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="trustSamlEmails"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="trust-saml-emails"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-full">Trust SAML emails</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="trustLdapEmails"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="trust-ldap-emails"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-full">Trust LDAP emails</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="trustOidcEmails"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="trust-oidc-emails"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-full">Trust OIDC emails</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting || !isDirty}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={TabSections.Encryption}>
|
||||||
|
<EncryptionPanel rootKmsDetails={serverRootKmsDetails} />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={TabSections.Auth}>
|
||||||
|
<AuthPanel />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={TabSections.RateLimit}>
|
||||||
|
<RateLimitPanel />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={TabSections.Integrations}>
|
||||||
|
<IntegrationPanel />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={TabSections.Users}>
|
||||||
|
<UserPanel />
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Button, FormControl, Switch } from "@app/components/v2";
|
||||||
|
import { useServerConfig } from "@app/context";
|
||||||
|
import { useUpdateServerConfig } from "@app/hooks/api";
|
||||||
|
import { LoginMethod } from "@app/hooks/api/admin/types";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
isEmailEnabled: z.boolean(),
|
||||||
|
isGoogleEnabled: z.boolean(),
|
||||||
|
isGithubEnabled: z.boolean(),
|
||||||
|
isGitlabEnabled: z.boolean(),
|
||||||
|
isSamlEnabled: z.boolean(),
|
||||||
|
isLdapEnabled: z.boolean(),
|
||||||
|
isOidcEnabled: z.boolean()
|
||||||
|
});
|
||||||
|
|
||||||
|
type TAuthForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const AuthPanel = () => {
|
||||||
|
const { config } = useServerConfig();
|
||||||
|
const { enabledLoginMethods } = config;
|
||||||
|
const { mutateAsync: updateServerConfig } = useUpdateServerConfig();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { isSubmitting, isDirty }
|
||||||
|
} = useForm<TAuthForm>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
// if not yet explicitly defined by the admin, all login methods should be enabled by default
|
||||||
|
values: enabledLoginMethods
|
||||||
|
? {
|
||||||
|
isEmailEnabled: enabledLoginMethods.includes(LoginMethod.EMAIL),
|
||||||
|
isGoogleEnabled: enabledLoginMethods.includes(LoginMethod.GOOGLE),
|
||||||
|
isGithubEnabled: enabledLoginMethods.includes(LoginMethod.GITHUB),
|
||||||
|
isGitlabEnabled: enabledLoginMethods.includes(LoginMethod.GITLAB),
|
||||||
|
isSamlEnabled: enabledLoginMethods.includes(LoginMethod.SAML),
|
||||||
|
isLdapEnabled: enabledLoginMethods.includes(LoginMethod.LDAP),
|
||||||
|
isOidcEnabled: enabledLoginMethods.includes(LoginMethod.OIDC)
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
isEmailEnabled: true,
|
||||||
|
isGoogleEnabled: true,
|
||||||
|
isGithubEnabled: true,
|
||||||
|
isGitlabEnabled: true,
|
||||||
|
isSamlEnabled: true,
|
||||||
|
isLdapEnabled: true,
|
||||||
|
isOidcEnabled: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onAuthFormSubmit = async (formData: TAuthForm) => {
|
||||||
|
try {
|
||||||
|
const enabledMethods: LoginMethod[] = [];
|
||||||
|
if (formData.isEmailEnabled) {
|
||||||
|
enabledMethods.push(LoginMethod.EMAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.isGoogleEnabled) {
|
||||||
|
enabledMethods.push(LoginMethod.GOOGLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.isGithubEnabled) {
|
||||||
|
enabledMethods.push(LoginMethod.GITHUB);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.isGitlabEnabled) {
|
||||||
|
enabledMethods.push(LoginMethod.GITLAB);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.isSamlEnabled) {
|
||||||
|
enabledMethods.push(LoginMethod.SAML);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.isLdapEnabled) {
|
||||||
|
enabledMethods.push(LoginMethod.LDAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.isOidcEnabled) {
|
||||||
|
enabledMethods.push(LoginMethod.OIDC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enabledMethods.length) {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "At least one login method should be enabled."
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateServerConfig({
|
||||||
|
enabledLoginMethods: enabledMethods
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Login methods have been successfully updated.",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to update login methods."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||||
|
onSubmit={handleSubmit(onAuthFormSubmit)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col justify-start">
|
||||||
|
<div className="mb-2 text-xl font-semibold text-mineshaft-100">Login Methods</div>
|
||||||
|
<div className="mb-4 max-w-sm text-sm text-mineshaft-400">
|
||||||
|
Select the login methods you wish to allow for all users of this instance.
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isEmailEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="email-enabled"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-24">Email</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isGoogleEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="google-enabled"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-24">Google SSO</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isGithubEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="enable-github"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-24">Github SSO</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isGitlabEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="enable-gitlab"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-24">Gitlab SSO</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isSamlEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="enable-saml"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-24">SAML SSO</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isOidcEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="enable-oidc"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-24">OIDC SSO</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="isLdapEnabled"
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Switch
|
||||||
|
id="enable-ldap"
|
||||||
|
onCheckedChange={(value) => field.onChange(value)}
|
||||||
|
isChecked={field.value}
|
||||||
|
>
|
||||||
|
<p className="w-24">LDAP</p>
|
||||||
|
</Switch>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className="mt-2"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting || !isDirty}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Button, FormControl, Select, SelectItem } from "@app/components/v2";
|
||||||
|
import { useSubscription } from "@app/context";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import { useUpdateServerEncryptionStrategy } from "@app/hooks/api";
|
||||||
|
import {
|
||||||
|
RootKeyEncryptionStrategy,
|
||||||
|
TGetServerRootKmsEncryptionDetails
|
||||||
|
} from "@app/hooks/api/admin/types";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
encryptionStrategy: z.nativeEnum(RootKeyEncryptionStrategy)
|
||||||
|
});
|
||||||
|
|
||||||
|
const strategies: Record<RootKeyEncryptionStrategy, string> = {
|
||||||
|
[RootKeyEncryptionStrategy.Software]: "Software-based Encryption",
|
||||||
|
[RootKeyEncryptionStrategy.HSM]: "Hardware Security Module (HSM)"
|
||||||
|
};
|
||||||
|
|
||||||
|
type TForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
rootKmsDetails?: TGetServerRootKmsEncryptionDetails;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EncryptionPanel = ({ rootKmsDetails }: Props) => {
|
||||||
|
const { mutateAsync: updateEncryptionStrategy } = useUpdateServerEncryptionStrategy();
|
||||||
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { isSubmitting, isDirty }
|
||||||
|
} = useForm<TForm>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
values: {
|
||||||
|
encryptionStrategy:
|
||||||
|
rootKmsDetails?.strategies?.find((s) => s.enabled)?.strategy ??
|
||||||
|
RootKeyEncryptionStrategy.Software
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = useCallback(async (formData: TForm) => {
|
||||||
|
if (!subscription) return;
|
||||||
|
|
||||||
|
if (!subscription.hsm) {
|
||||||
|
handlePopUpOpen("upgradePlan", {
|
||||||
|
description: "Hardware Security Module's (HSM's), are only available on Enterprise plans."
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateEncryptionStrategy(formData.encryptionStrategy);
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Encryption strategy updated successfully"
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to update encryption strategy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<form
|
||||||
|
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col justify-start">
|
||||||
|
<div className="flex w-full justify-between">
|
||||||
|
<div className="mb-2 text-xl font-semibold text-mineshaft-100">
|
||||||
|
KMS Encryption Strategy
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4 max-w-sm text-sm text-mineshaft-400">
|
||||||
|
Select which type of encryption strategy you want to use for your KMS root key. HSM is
|
||||||
|
supported on Enterprise plans.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!!rootKmsDetails && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="encryptionStrategy"
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
className="max-w-sm"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
className="w-full bg-mineshaft-700"
|
||||||
|
dropdownContainerClassName="bg-mineshaft-800"
|
||||||
|
defaultValue={field.value}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
{...field}
|
||||||
|
>
|
||||||
|
{rootKmsDetails.strategies?.map((strategy) => (
|
||||||
|
<SelectItem key={strategy.strategy} value={strategy.strategy}>
|
||||||
|
{strategies[strategy.strategy]}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="mt-2"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting || !isDirty}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
<UpgradePlanModal
|
||||||
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
|
text={(popUp.upgradePlan?.data as { description: string })?.description}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Button, FormControl, Input } from "@app/components/v2";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
import { useGetAdminSlackConfig, useUpdateServerConfig } from "@app/hooks/api";
|
||||||
|
|
||||||
|
const slackFormSchema = z.object({
|
||||||
|
clientId: z.string(),
|
||||||
|
clientSecret: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
type TSlackForm = z.infer<typeof slackFormSchema>;
|
||||||
|
|
||||||
|
const getCustomSlackAppCreationUrl = () =>
|
||||||
|
`https://api.slack.com/apps?new_app=1&manifest_json=${encodeURIComponent(
|
||||||
|
JSON.stringify({
|
||||||
|
display_information: {
|
||||||
|
name: "Infisical",
|
||||||
|
description: "Get real-time Infisical updates in Slack",
|
||||||
|
background_color: "#c2d62b",
|
||||||
|
long_description: `This Slack application is designed specifically for use with your self-hosted Infisical instance, allowing seamless integration between your Infisical projects and your Slack workspace. With this integration, your team can stay up-to-date with the latest events, changes, and notifications directly inside Slack.
|
||||||
|
- Notifications: Receive real-time updates and alerts about critical events in your Infisical projects. Whether it's a new project being created, updates to secrets, or changes to your team's configuration, you will be promptly notified within the designated Slack channels of your choice.
|
||||||
|
- Customization: Tailor the notifications to your team's specific needs by configuring which types of events trigger alerts and in which channels they are sent.
|
||||||
|
- Collaboration: Keep your entire team in the loop with notifications that help facilitate more efficient collaboration by ensuring that everyone is aware of important developments in your Infisical projects.
|
||||||
|
|
||||||
|
By integrating Infisical with Slack, you can enhance your workflow by combining the power of secure secrets management with the communication capabilities of Slack.`
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
app_home: {
|
||||||
|
home_tab_enabled: false,
|
||||||
|
messages_tab_enabled: false,
|
||||||
|
messages_tab_read_only_enabled: true
|
||||||
|
},
|
||||||
|
bot_user: {
|
||||||
|
display_name: "Infisical",
|
||||||
|
always_online: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
oauth_config: {
|
||||||
|
redirect_urls: [`${window.origin}/api/v1/workflow-integrations/slack/oauth_redirect`],
|
||||||
|
scopes: {
|
||||||
|
bot: ["chat:write.public", "chat:write", "channels:read", "groups:read"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
org_deploy_enabled: false,
|
||||||
|
socket_mode_enabled: false,
|
||||||
|
token_rotation_enabled: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
export const IntegrationPanel = () => {
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
formState: { isSubmitting, isDirty }
|
||||||
|
} = useForm<TSlackForm>({
|
||||||
|
resolver: zodResolver(slackFormSchema)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: adminSlackConfig } = useGetAdminSlackConfig();
|
||||||
|
const { mutateAsync: updateAdminServerConfig } = useUpdateServerConfig();
|
||||||
|
const [isSlackClientIdFocused, setIsSlackClientIdFocused] = useToggle();
|
||||||
|
const [isSlackClientSecretFocused, setIsSlackClientSecretFocused] = useToggle();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (adminSlackConfig) {
|
||||||
|
setValue("clientId", adminSlackConfig.clientId);
|
||||||
|
setValue("clientSecret", adminSlackConfig.clientSecret);
|
||||||
|
}
|
||||||
|
}, [adminSlackConfig]);
|
||||||
|
|
||||||
|
const onSlackFormSubmit = async (data: TSlackForm) => {
|
||||||
|
await updateAdminServerConfig({
|
||||||
|
slackClientId: data.clientId,
|
||||||
|
slackClientSecret: data.clientSecret
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Updated admin slack configuration",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||||
|
onSubmit={handleSubmit(onSlackFormSubmit)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col justify-start">
|
||||||
|
<div className="mb-2 text-xl font-semibold text-mineshaft-100">Slack Integration</div>
|
||||||
|
<div className="mb-4 max-w-lg text-sm text-mineshaft-300">
|
||||||
|
Step 1: Create your Infisical Slack App
|
||||||
|
</div>
|
||||||
|
<div className="mb-6">
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
onClick={() => window.open(getCustomSlackAppCreationUrl())}
|
||||||
|
>
|
||||||
|
Create Slack App
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4 max-w-lg text-sm text-mineshaft-300">
|
||||||
|
Step 2: Configure your instance-wide settings to enable integration with Slack. Copy the
|
||||||
|
values from the App Credentials page of your custom Slack App.
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="clientId"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Client ID"
|
||||||
|
className="w-96"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
value={field.value || ""}
|
||||||
|
type={isSlackClientIdFocused ? "text" : "password"}
|
||||||
|
onFocus={() => setIsSlackClientIdFocused.on()}
|
||||||
|
onBlur={() => setIsSlackClientIdFocused.off()}
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="clientSecret"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Client Secret"
|
||||||
|
className="w-96"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
value={field.value || ""}
|
||||||
|
type={isSlackClientSecretFocused ? "text" : "password"}
|
||||||
|
onFocus={() => setIsSlackClientSecretFocused.on()}
|
||||||
|
onBlur={() => setIsSlackClientSecretFocused.off()}
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="mt-2"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting || !isDirty}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Button, ContentLoader, FormControl, Input } from "@app/components/v2";
|
||||||
|
import { useSubscription } from "@app/context";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import { useGetRateLimit, useUpdateRateLimit } from "@app/hooks/api";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
readRateLimit: z.number(),
|
||||||
|
writeRateLimit: z.number(),
|
||||||
|
secretsRateLimit: z.number(),
|
||||||
|
authRateLimit: z.number(),
|
||||||
|
inviteUserRateLimit: z.number(),
|
||||||
|
mfaRateLimit: z.number(),
|
||||||
|
publicEndpointLimit: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
type TRateLimitForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const RateLimitPanel = () => {
|
||||||
|
const { data: rateLimit, isPending } = useGetRateLimit();
|
||||||
|
const { subscription } = useSubscription();
|
||||||
|
const { mutateAsync: updateRateLimit } = useUpdateRateLimit();
|
||||||
|
const { handlePopUpToggle, handlePopUpOpen, popUp } = usePopUp(["upgradePlan"] as const);
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { isSubmitting, isDirty }
|
||||||
|
} = useForm<TRateLimitForm>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
values: {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
readRateLimit: rateLimit?.readRateLimit ?? 600,
|
||||||
|
writeRateLimit: rateLimit?.writeRateLimit ?? 200,
|
||||||
|
secretsRateLimit: rateLimit?.secretsRateLimit ?? 60,
|
||||||
|
authRateLimit: rateLimit?.authRateLimit ?? 60,
|
||||||
|
inviteUserRateLimit: rateLimit?.inviteUserRateLimit ?? 30,
|
||||||
|
mfaRateLimit: rateLimit?.mfaRateLimit ?? 20,
|
||||||
|
publicEndpointLimit: rateLimit?.publicEndpointLimit ?? 30
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const onRateLimitFormSubmit = async (formData: TRateLimitForm) => {
|
||||||
|
try {
|
||||||
|
if (subscription && !subscription.customRateLimits) {
|
||||||
|
handlePopUpOpen("upgradePlan");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
readRateLimit,
|
||||||
|
writeRateLimit,
|
||||||
|
secretsRateLimit,
|
||||||
|
authRateLimit,
|
||||||
|
inviteUserRateLimit,
|
||||||
|
mfaRateLimit,
|
||||||
|
publicEndpointLimit
|
||||||
|
} = formData;
|
||||||
|
|
||||||
|
await updateRateLimit({
|
||||||
|
readRateLimit,
|
||||||
|
writeRateLimit,
|
||||||
|
secretsRateLimit,
|
||||||
|
authRateLimit,
|
||||||
|
inviteUserRateLimit,
|
||||||
|
mfaRateLimit,
|
||||||
|
publicEndpointLimit
|
||||||
|
});
|
||||||
|
createNotification({
|
||||||
|
text: "Rate limits have been successfully updated. Please allow at least 10 minutes for the changes to take effect.",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to update rate limiting setting."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return isPending ? (
|
||||||
|
<ContentLoader />
|
||||||
|
) : (
|
||||||
|
<form
|
||||||
|
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||||
|
onSubmit={handleSubmit(onRateLimitFormSubmit)}
|
||||||
|
>
|
||||||
|
<div className="mb-8 flex flex-col justify-start">
|
||||||
|
<div className="mb-4 text-xl font-semibold text-mineshaft-100">Configure rate limits</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="readRateLimit"
|
||||||
|
defaultValue={300}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Global read requests per minute"
|
||||||
|
className="w-72"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
value={field.value}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue={300}
|
||||||
|
name="writeRateLimit"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Global write requests per minute"
|
||||||
|
className="w-72"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
value={field.value || ""}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue={300}
|
||||||
|
name="secretsRateLimit"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Secret requests per minute"
|
||||||
|
className="w-72"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
value={field.value || ""}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue={300}
|
||||||
|
name="authRateLimit"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Auth requests per minute"
|
||||||
|
className="w-72"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
value={field.value || ""}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue={300}
|
||||||
|
name="inviteUserRateLimit"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="User invitation requests per minute"
|
||||||
|
className="w-72"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
value={field.value || ""}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue={300}
|
||||||
|
name="mfaRateLimit"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Multi factor auth requests per minute"
|
||||||
|
className="w-72"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
value={field.value || ""}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue={300}
|
||||||
|
name="publicEndpointLimit"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Secret sharing requests per minute"
|
||||||
|
className="w-72"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
value={field.value || ""}
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button type="submit" isLoading={isSubmitting} isDisabled={isSubmitting || !isDirty}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<UpgradePlanModal
|
||||||
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
|
text="You can configure custom rate limits if you switch to Infisical's Enterprise plan."
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { faMagnifyingGlass, faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
DeleteActionModal,
|
||||||
|
EmptyState,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
Table,
|
||||||
|
TableContainer,
|
||||||
|
TableSkeleton,
|
||||||
|
TBody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
THead,
|
||||||
|
Tr
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useSubscription, useUser } from "@app/context";
|
||||||
|
import { useDebounce, usePopUp } from "@app/hooks";
|
||||||
|
import { useAdminDeleteUser, useAdminGetUsers } from "@app/hooks/api";
|
||||||
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
const UserPanelTable = ({
|
||||||
|
handlePopUpOpen
|
||||||
|
}: {
|
||||||
|
handlePopUpOpen: (
|
||||||
|
popUpName: keyof UsePopUpState<["removeUser", "upgradePlan"]>,
|
||||||
|
data?: {
|
||||||
|
username: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
) => void;
|
||||||
|
}) => {
|
||||||
|
const [searchUserFilter, setSearchUserFilter] = useState("");
|
||||||
|
const { user } = useUser();
|
||||||
|
const userId = user?.id || "";
|
||||||
|
const [debounedSearchTerm] = useDebounce(searchUserFilter, 500);
|
||||||
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
|
const { data, isPending, isFetchingNextPage, hasNextPage, fetchNextPage } = useAdminGetUsers({
|
||||||
|
limit: 20,
|
||||||
|
searchTerm: debounedSearchTerm
|
||||||
|
});
|
||||||
|
|
||||||
|
const isEmpty = !isPending && !data?.pages?.[0].length;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Input
|
||||||
|
value={searchUserFilter}
|
||||||
|
onChange={(e) => setSearchUserFilter(e.target.value)}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||||
|
placeholder="Search users..."
|
||||||
|
/>
|
||||||
|
<div className="mt-4">
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th className="w-5/12">Name</Th>
|
||||||
|
<Th className="w-5/12">Username</Th>
|
||||||
|
<Th className="w-5" />
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
{isPending && <TableSkeleton columns={4} innerKey="users" />}
|
||||||
|
{!isPending &&
|
||||||
|
data?.pages?.map((users) =>
|
||||||
|
users.map(({ username, email, firstName, lastName, id, superAdmin }) => {
|
||||||
|
const name = firstName || lastName ? `${firstName} ${lastName}` : "-";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr key={`user-${id}`} className="w-full">
|
||||||
|
<Td className="w-5/12">
|
||||||
|
{name}
|
||||||
|
{superAdmin && (
|
||||||
|
<Badge variant="primary" className="ml-2">
|
||||||
|
Server Admin
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td className="w-5/12">{email}</Td>
|
||||||
|
<Td>
|
||||||
|
{userId !== id && (
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<IconButton
|
||||||
|
size="lg"
|
||||||
|
colorSchema="danger"
|
||||||
|
variant="plain"
|
||||||
|
ariaLabel="update"
|
||||||
|
isDisabled={userId === id}
|
||||||
|
onClick={() => {
|
||||||
|
if (!subscription?.instanceUserManagement) {
|
||||||
|
handlePopUpOpen("upgradePlan");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handlePopUpOpen("removeUser", { username, id });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faXmark} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
{!isPending && isEmpty && <EmptyState title="No users found" icon={faUsers} />}
|
||||||
|
</TableContainer>
|
||||||
|
{!isEmpty && (
|
||||||
|
<Button
|
||||||
|
className="mt-4 py-3 text-sm"
|
||||||
|
isFullWidth
|
||||||
|
variant="star"
|
||||||
|
isLoading={isFetchingNextPage}
|
||||||
|
isDisabled={isFetchingNextPage || !hasNextPage}
|
||||||
|
onClick={() => fetchNextPage()}
|
||||||
|
>
|
||||||
|
{hasNextPage ? "Load More" : "End of list"}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UserPanel = () => {
|
||||||
|
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||||
|
"removeUser",
|
||||||
|
"upgradePlan"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const { mutateAsync: deleteUser } = useAdminDeleteUser();
|
||||||
|
|
||||||
|
const handleRemoveUser = async () => {
|
||||||
|
const { id } = popUp?.removeUser?.data as { id: string; username: string };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteUser(id);
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Successfully deleted user"
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Error deleting user"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePopUpClose("removeUser");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
|
<div className="mb-4">
|
||||||
|
<p className="text-xl font-semibold text-mineshaft-100">Users</p>
|
||||||
|
</div>
|
||||||
|
<UserPanelTable handlePopUpOpen={handlePopUpOpen} />
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={popUp.removeUser.isOpen}
|
||||||
|
deleteKey="remove"
|
||||||
|
title={`Are you sure you want to delete User with username ${
|
||||||
|
(popUp?.removeUser?.data as { id: string; username: string })?.username || ""
|
||||||
|
}?`}
|
||||||
|
onChange={(isOpen) => handlePopUpToggle("removeUser", isOpen)}
|
||||||
|
onDeleteApproved={handleRemoveUser}
|
||||||
|
/>
|
||||||
|
<UpgradePlanModal
|
||||||
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
|
text="Deleting users via Admin UI is only available on Infisical's Pro plan and above."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { OverviewPage } from "./OverviewPage";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_authenticate/admin/_admin-layout/")({
|
||||||
|
component: OverviewPage
|
||||||
|
});
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { generateBackupPDFAsync } from "@app/components/utilities/generateBackupPDF";
|
||||||
|
// TODO(akhilmhdh): rewrite this into module functions in lib
|
||||||
|
import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage";
|
||||||
|
import SecurityClient from "@app/components/utilities/SecurityClient";
|
||||||
|
import { Button, ContentLoader, FormControl, Input } from "@app/components/v2";
|
||||||
|
import { useServerConfig } from "@app/context";
|
||||||
|
import { useCreateAdminUser, useSelectOrganization } from "@app/hooks/api";
|
||||||
|
import { generateUserBackupKey, generateUserPassKey } from "@app/lib/crypto";
|
||||||
|
|
||||||
|
import { DownloadBackupKeys } from "./components/DownloadBackupKeys";
|
||||||
|
|
||||||
|
const formSchema = z
|
||||||
|
.object({
|
||||||
|
email: z.string().email().trim(),
|
||||||
|
firstName: z.string().trim(),
|
||||||
|
lastName: z.string().trim().optional(),
|
||||||
|
password: z.string().trim().min(14).max(100),
|
||||||
|
confirmPassword: z.string().trim()
|
||||||
|
})
|
||||||
|
.refine((data) => data.password === data.confirmPassword, {
|
||||||
|
message: "Password don't match",
|
||||||
|
path: ["confirmPassword"]
|
||||||
|
});
|
||||||
|
|
||||||
|
type TFormSchema = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
enum SignupSteps {
|
||||||
|
DetailsForm = "details-form",
|
||||||
|
BackupKey = "backup-key"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SignUpPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
getValues,
|
||||||
|
formState: { isSubmitting }
|
||||||
|
} = useForm<TFormSchema>({
|
||||||
|
resolver: zodResolver(formSchema)
|
||||||
|
});
|
||||||
|
|
||||||
|
const [step, setStep] = useState(SignupSteps.DetailsForm);
|
||||||
|
|
||||||
|
const { config } = useServerConfig();
|
||||||
|
|
||||||
|
// TODO(rbr): do this is layout
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (config?.initialized) {
|
||||||
|
// if (isLoggedIn()) {
|
||||||
|
// navigate.push("/admin");
|
||||||
|
// } else {
|
||||||
|
// navigate.push("/login");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
const { mutateAsync: createAdminUser } = useCreateAdminUser();
|
||||||
|
const { mutateAsync: selectOrganization } = useSelectOrganization();
|
||||||
|
|
||||||
|
const handleFormSubmit = async ({ email, password, firstName, lastName }: TFormSchema) => {
|
||||||
|
// avoid multi submission
|
||||||
|
if (isSubmitting) return;
|
||||||
|
try {
|
||||||
|
const { privateKey, ...userPass } = await generateUserPassKey(email, password);
|
||||||
|
const res = await createAdminUser({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
...userPass
|
||||||
|
});
|
||||||
|
|
||||||
|
SecurityClient.setToken(res.token);
|
||||||
|
saveTokenToLocalStorage({
|
||||||
|
publicKey: userPass.publicKey,
|
||||||
|
encryptedPrivateKey: userPass.encryptedPrivateKey,
|
||||||
|
iv: userPass.encryptedPrivateKeyIV,
|
||||||
|
tag: userPass.encryptedPrivateKeyTag,
|
||||||
|
privateKey
|
||||||
|
});
|
||||||
|
await selectOrganization({ organizationId: res.organization.id });
|
||||||
|
|
||||||
|
// TODO(akhilmhdh): This is such a confusing pattern and too unreliable
|
||||||
|
// Will be refactored in next iteration to make it url based rather than local storage ones
|
||||||
|
// Part of migration to nextjs 14
|
||||||
|
localStorage.setItem("orgData.id", res.organization.id);
|
||||||
|
setStep(SignupSteps.BackupKey);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to create admin"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBackupKeyGenerate = async () => {
|
||||||
|
try {
|
||||||
|
const { email, password, firstName, lastName } = getValues();
|
||||||
|
const generatedKey = await generateUserBackupKey(email, password);
|
||||||
|
await generateBackupPDFAsync({
|
||||||
|
generatedKey,
|
||||||
|
personalEmail: email,
|
||||||
|
personalName: `${firstName} ${lastName}`
|
||||||
|
});
|
||||||
|
navigate({ to: "/admin" });
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to generate backup"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (config?.initialized && step === SignupSteps.DetailsForm)
|
||||||
|
return <ContentLoader text="Redirecting to admin page..." />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex max-h-screen min-h-screen flex-col justify-center overflow-y-auto bg-gradient-to-tr from-mineshaft-600 via-mineshaft-800 to-bunker-700 px-6">
|
||||||
|
<Helmet>
|
||||||
|
<title>{t("common.head-title", { title: t("signup.title") })}</title>
|
||||||
|
<link rel="icon" href="/infisical.ico" />
|
||||||
|
<meta property="og:image" content="/images/message.png" />
|
||||||
|
<meta property="og:title" content={t("signup.og-title") ?? ""} />
|
||||||
|
<meta name="og:description" content={t("signup.og-description") ?? ""} />
|
||||||
|
</Helmet>
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{step === SignupSteps.DetailsForm && (
|
||||||
|
<motion.div
|
||||||
|
className="text-mineshaft-200"
|
||||||
|
key="panel-1"
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center space-y-2 text-center">
|
||||||
|
<img src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical logo" />
|
||||||
|
<div className="pt-4 text-4xl">Welcome to Infisical</div>
|
||||||
|
<div className="pb-4 text-bunker-300">Create your first Super Admin Account</div>
|
||||||
|
</div>
|
||||||
|
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||||
|
<div className="mt-8">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="firstName"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="First name"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input isFullWidth size="md" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="lastName"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Last name"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input isFullWidth size="md" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="email"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Email"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input isFullWidth size="md" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="password"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Password"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input isFullWidth size="md" type="password" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="confirmPassword"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Confirm password"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input isFullWidth size="md" type="password" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
isFullWidth
|
||||||
|
className="mt-4"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
{step === SignupSteps.BackupKey && (
|
||||||
|
<motion.div
|
||||||
|
className="text-mineshaft-200"
|
||||||
|
key="panel-2"
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
initial={{ opacity: 0, translateX: 30 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
|
>
|
||||||
|
<DownloadBackupKeys onGenerate={handleBackupKeyGenerate} />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { Button } from "@app/components/v2";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onGenerate: () => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DownloadBackupKeys = ({ onGenerate }: Props): JSX.Element => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [isLoading, setIsLoading] = useToggle();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto mb-36 flex h-full w-full flex-col items-center md:mb-16 md:px-6">
|
||||||
|
<p className="flex flex-col items-center justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faWarning}
|
||||||
|
className="mb-6 ml-2 mr-3 pt-1 text-6xl text-bunker-200"
|
||||||
|
/>
|
||||||
|
{t("signup.step4-message")}
|
||||||
|
</p>
|
||||||
|
<div className="text-md mt-8 flex w-full max-w-md flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 pb-2 text-center text-bunker-300 md:min-w-[24rem] lg:w-1/6">
|
||||||
|
<div className="m-2 mx-auto mt-4 flex w-full flex-row items-center rounded-md px-3 text-center text-bunker-300 md:mt-8 md:min-w-[23rem] lg:w-1/6">
|
||||||
|
<span className="mb-2">
|
||||||
|
{t("signup.step4-description1")} {t("signup.step4-description3")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mx-auto mb-2 mt-2 flex w-full flex-col items-center justify-center px-3 text-center text-sm md:mb-4 md:mt-4 md:min-w-[20rem] md:max-w-md md:text-left lg:w-1/6">
|
||||||
|
<div className="text-l w-full py-1 text-lg">
|
||||||
|
<Button
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading.on();
|
||||||
|
await onGenerate();
|
||||||
|
} finally {
|
||||||
|
setIsLoading.off();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
size="sm"
|
||||||
|
isFullWidth
|
||||||
|
className="h-12"
|
||||||
|
colorSchema="primary"
|
||||||
|
variant="outline_bg"
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
Download PDF
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { DownloadBackupKeys } from "./DownloadBackupKeys";
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { SignUpPage } from "./SignUpPage";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_restrict-login-signup/admin/signup")({
|
||||||
|
component: SignUpPage,
|
||||||
|
beforeLoad: ({ context }) => {
|
||||||
|
if (context.serverConfig.initialized) {
|
||||||
|
throw redirect({
|
||||||
|
to: "/login"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { AdminLayout } from "@app/layouts/AdminLayout";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_authenticate/admin/_admin-layout")({
|
||||||
|
component: AdminLayout,
|
||||||
|
beforeLoad: ({ context }) => {
|
||||||
|
if (!context.user.superAdmin) {
|
||||||
|
throw redirect({
|
||||||
|
to: "/organization/secret-manager/overview"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -2,6 +2,6 @@ import { createFileRoute } from "@tanstack/react-router";
|
|||||||
|
|
||||||
import { CliRedirectPage } from "./CliRedirectPage";
|
import { CliRedirectPage } from "./CliRedirectPage";
|
||||||
|
|
||||||
export const Route = createFileRoute("/_authenticate/cli-redirect")({
|
export const Route = createFileRoute("/_restrict-login-signup/cli-redirect")({
|
||||||
component: CliRedirectPage
|
component: CliRedirectPage
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { EmailNotVerifiedPage } from "./EmailNotVerifiedPage";
|
import { EmailNotVerifiedPage } from "./EmailNotVerifiedPage";
|
||||||
|
|
||||||
export const Route = createFileRoute("/_restrict-login-signup/email-not-verified")({
|
export const Route = createFileRoute("/_restrict-login-signup/email-not-verified")({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { LoginPage } from './LoginPage'
|
import { LoginPage } from "./LoginPage";
|
||||||
|
|
||||||
export const Route = createFileRoute('/_restrict-login-signup/login/')({
|
export const Route = createFileRoute("/_restrict-login-signup/login/")({
|
||||||
component: LoginPage,
|
component: LoginPage
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Link, useSearch } from "@tanstack/react-router";
|
import { Link, useSearch } from "@tanstack/react-router";
|
||||||
import { jwtDecode } from "jwt-decode";
|
import { jwtDecode } from "jwt-decode";
|
||||||
|
|
||||||
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
|
|
||||||
import { PasswordStep } from "../LoginPage/components";
|
import { PasswordStep } from "../LoginPage/components";
|
||||||
|
|
||||||
export const LoginSsoPage = () => {
|
export const LoginSsoPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const search = useSearch({ from: "/_restrict-login-signup/login/sso/" });
|
const search = useSearch({ from: ROUTE_PATHS.Auth.LoginSSO.id });
|
||||||
const token = search.token as string;
|
const token = search.token as string;
|
||||||
const [step, setStep] = useState(0);
|
const [step, setStep] = useState(0);
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { LoginProviderErrorPage } from './ProviderErrorPage'
|
import { LoginProviderErrorPage } from "./ProviderErrorPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute("/_restrict-login-signup/login/provider/error")({
|
||||||
'/_restrict-login-signup/login/provider/error',
|
component: LoginProviderErrorPage
|
||||||
)({
|
});
|
||||||
component: LoginProviderErrorPage,
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { zodValidator } from '@tanstack/zod-adapter'
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from 'zod'
|
import { z } from "zod";
|
||||||
|
|
||||||
import { LoginProviderSuccessPage } from './ProviderSuccessPage'
|
import { LoginProviderSuccessPage } from "./ProviderSuccessPage";
|
||||||
|
|
||||||
const LoginProviderSuccessQuerySchema = z.object({
|
const LoginProviderSuccessQuerySchema = z.object({
|
||||||
token: z.string(),
|
token: z.string()
|
||||||
})
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute("/_restrict-login-signup/login/provider/success")({
|
||||||
'/_restrict-login-signup/login/provider/success',
|
|
||||||
)({
|
|
||||||
component: LoginProviderSuccessPage,
|
component: LoginProviderSuccessPage,
|
||||||
validateSearch: zodValidator(LoginProviderSuccessQuerySchema),
|
validateSearch: zodValidator(LoginProviderSuccessQuerySchema)
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { SelectOrganizationPage } from './SelectOrgPage'
|
import { SelectOrganizationPage } from "./SelectOrgPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute("/_restrict-login-signup/login/select-organization")({
|
||||||
'/_restrict-login-signup/login/select-organization',
|
component: SelectOrganizationPage
|
||||||
)({
|
});
|
||||||
component: SelectOrganizationPage,
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { zodValidator } from '@tanstack/zod-adapter'
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from 'zod'
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SignupSsoPage } from './SignUpSsoPage'
|
import { SignupSsoPage } from "./SignUpSsoPage";
|
||||||
|
|
||||||
const SignupSSOPageQueryParamsSchema = z.object({
|
const SignupSSOPageQueryParamsSchema = z.object({
|
||||||
token: z.string(),
|
token: z.string()
|
||||||
})
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute('/_restrict-login-signup/signup/sso')({
|
export const Route = createFileRoute("/_restrict-login-signup/signup/sso")({
|
||||||
component: SignupSsoPage,
|
component: SignupSsoPage,
|
||||||
validateSearch: zodValidator(SignupSSOPageQueryParamsSchema),
|
validateSearch: zodValidator(SignupSSOPageQueryParamsSchema)
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { VerifyEmailPage } from "./VerifyEmailPage";
|
import { VerifyEmailPage } from "./VerifyEmailPage";
|
||||||
|
|
||||||
export const Route = createFileRoute("/_restrict-login-signup/verify-email")({
|
export const Route = createFileRoute("/_restrict-login-signup/verify-email")({
|
||||||
|
|||||||
+10
-2
@@ -63,7 +63,12 @@ const Page = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
handlePopUpClose("deleteCa");
|
handlePopUpClose("deleteCa");
|
||||||
navigate({ to: `/${ProjectType.CertificateManager}/${projectId}/certificates` as const });
|
navigate({
|
||||||
|
to: `/${ProjectType.CertificateManager}/$projectId/overview` as const,
|
||||||
|
params: {
|
||||||
|
projectId
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Failed to delete CA",
|
text: "Failed to delete CA",
|
||||||
@@ -82,7 +87,10 @@ const Page = () => {
|
|||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate({
|
navigate({
|
||||||
to: `/${ProjectType.CertificateManager}/${projectId}/certificates` as const
|
to: `/${ProjectType.CertificateManager}/${projectId}/overview` as const,
|
||||||
|
params: {
|
||||||
|
projectId
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="mb-4"
|
className="mb-4"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } 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
|
||||||
})
|
});
|
||||||
|
|||||||
+1
-1
@@ -65,7 +65,7 @@ export const ExternalCaInstallForm = ({ caId, handlePopUpToggle }: Props) => {
|
|||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
handlePopUpToggle("installCaCert", false);
|
handlePopUpToggle("installCaCert", false);
|
||||||
} catch (err) {
|
} catch {
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Failed to install certificate for CA",
|
text: "Failed to install certificate for CA",
|
||||||
type: "error"
|
type: "error"
|
||||||
|
|||||||
+1
-1
@@ -126,7 +126,7 @@ export const InternalCaInstallForm = ({ caId, handlePopUpToggle }: Props) => {
|
|||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
handlePopUpToggle("installCaCert", false);
|
handlePopUpToggle("installCaCert", false);
|
||||||
} catch (err) {
|
} catch {
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Failed to install certificate for CA",
|
text: "Failed to install certificate for CA",
|
||||||
type: "error"
|
type: "error"
|
||||||
|
|||||||
+5
-1
@@ -78,7 +78,11 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
key={`ca-${ca.id}`}
|
key={`ca-${ca.id}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate({
|
navigate({
|
||||||
to: `/${ProjectType.CertificateManager}/${currentWorkspace?.id}/ca/${ca.id}` as const
|
to: `/${ProjectType.CertificateManager}/$projectId/ca/$caId` as const,
|
||||||
|
params: {
|
||||||
|
projectId: currentWorkspace.id,
|
||||||
|
caId: ca.id
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
+2
-2
@@ -407,7 +407,7 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
|
|||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
>
|
>
|
||||||
<div className="mt-2 mb-7 grid grid-cols-2 gap-2">
|
<div className="mb-7 mt-2 grid grid-cols-2 gap-2">
|
||||||
{KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => {
|
{KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => {
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -441,7 +441,7 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
|
|||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
>
|
>
|
||||||
<div className="mt-2 mb-7 grid grid-cols-2 gap-2">
|
<div className="mb-7 mt-2 grid grid-cols-2 gap-2">
|
||||||
{EXTENDED_KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => {
|
{EXTENDED_KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => {
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
+6
-2
@@ -76,14 +76,18 @@ export const PkiCollectionModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// create
|
// create
|
||||||
const { id: createdId } = await createPkiCollection({
|
const { id: collectionId } = await createPkiCollection({
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
navigate({
|
navigate({
|
||||||
to: `/${ProjectType.CertificateManager}/${projectId}/pki-collections/${createdId}` as const
|
to: `/${ProjectType.CertificateManager}/$projectId/pki-collections/$collectionId` as const,
|
||||||
|
params: {
|
||||||
|
projectId,
|
||||||
|
collectionId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -61,7 +61,11 @@ export const PkiCollectionTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
key={`pki-collection-${pkiCollection.id}`}
|
key={`pki-collection-${pkiCollection.id}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate({
|
navigate({
|
||||||
to: `/${ProjectType.CertificateManager}/${projectId}/pki-collections/${pkiCollection.id}` as const
|
to: `/${ProjectType.CertificateManager}/$projectId/pki-collections/$collectionId` as const,
|
||||||
|
params: {
|
||||||
|
projectId,
|
||||||
|
collectionId: pkiCollection.id
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { CertificatesPage } from './CertificatesPage'
|
import { CertificatesPage } from "./CertificatesPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/overview',
|
"/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/overview"
|
||||||
)({
|
)({
|
||||||
component: CertificatesPage,
|
component: CertificatesPage
|
||||||
})
|
});
|
||||||
|
|||||||
+8
-2
@@ -58,7 +58,10 @@ export const PkiCollectionPage = () => {
|
|||||||
});
|
});
|
||||||
handlePopUpClose("deletePkiCollection");
|
handlePopUpClose("deletePkiCollection");
|
||||||
navigate({
|
navigate({
|
||||||
to: `/${ProjectType.CertificateManager}/${projectId}/certificates` as const
|
to: `/${ProjectType.CertificateManager}/$projectId/overview` as const,
|
||||||
|
params: {
|
||||||
|
projectId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
createNotification({
|
createNotification({
|
||||||
@@ -78,7 +81,10 @@ export const PkiCollectionPage = () => {
|
|||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate({
|
navigate({
|
||||||
to: `/${ProjectType.CertificateManager}/${projectId}/certificates` as const
|
to: `/${ProjectType.CertificateManager}/$projectId/overview` as const,
|
||||||
|
params: {
|
||||||
|
projectId
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className="mb-4"
|
className="mb-4"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } 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
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,6 +1,2 @@
|
|||||||
export { AutoCapitalizationSection } from "./AutoCapitalizationSection";
|
|
||||||
export { BackfillSecretReferenceSecretion } from "./BackfillSecretReferenceSection";
|
|
||||||
export { DeleteProjectSection } from "./DeleteProjectSection";
|
export { DeleteProjectSection } from "./DeleteProjectSection";
|
||||||
export { EnvironmentSection } from "./EnvironmentSection";
|
|
||||||
export { ProjectOverviewChangeSection } from "./ProjectOverviewChangeSection";
|
export { ProjectOverviewChangeSection } from "./ProjectOverviewChangeSection";
|
||||||
export { SecretTagsSection } from "./SecretTagsSection";
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { SettingsPage } from './SettingsPage'
|
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
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { OverviewPage } from './OverviewPage'
|
import { OverviewPage } from "./OverviewPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/overview',
|
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/overview"
|
||||||
)({
|
)({
|
||||||
component: OverviewPage,
|
component: OverviewPage
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,6 +1,2 @@
|
|||||||
export { AutoCapitalizationSection } from "./AutoCapitalizationSection";
|
|
||||||
export { BackfillSecretReferenceSecretion } from "./BackfillSecretReferenceSection";
|
|
||||||
export { DeleteProjectSection } from "./DeleteProjectSection";
|
export { DeleteProjectSection } from "./DeleteProjectSection";
|
||||||
export { EnvironmentSection } from "./EnvironmentSection";
|
|
||||||
export { ProjectOverviewChangeSection } from "./ProjectOverviewChangeSection";
|
export { ProjectOverviewChangeSection } from "./ProjectOverviewChangeSection";
|
||||||
export { SecretTagsSection } from "./SecretTagsSection";
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { SettingsPage } from './SettingsPage'
|
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
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,29 +1,25 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { workspaceKeys } from '@app/hooks/api'
|
import { workspaceKeys } from "@app/hooks/api";
|
||||||
import {
|
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||||
fetchUserProjectPermissions,
|
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||||
roleQueryKeys,
|
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
||||||
} from '@app/hooks/api/roles/queries'
|
|
||||||
import { fetchWorkspaceById } from '@app/hooks/api/workspace/queries'
|
|
||||||
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: ProjectLayout,
|
component: ProjectLayout,
|
||||||
beforeLoad: async ({ params, context }) => {
|
beforeLoad: async ({ params, context }) => {
|
||||||
await context.queryClient.ensureQueryData({
|
await context.queryClient.ensureQueryData({
|
||||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||||
queryFn: () => fetchWorkspaceById(params.projectId),
|
queryFn: () => fetchWorkspaceById(params.projectId)
|
||||||
})
|
});
|
||||||
|
|
||||||
await context.queryClient.ensureQueryData({
|
await context.queryClient.ensureQueryData({
|
||||||
queryKey: roleQueryKeys.getUserProjectPermissions({
|
queryKey: roleQueryKeys.getUserProjectPermissions({
|
||||||
workspaceId: params.projectId,
|
workspaceId: params.projectId
|
||||||
}),
|
}),
|
||||||
queryFn: () =>
|
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
||||||
fetchUserProjectPermissions({ workspaceId: params.projectId }),
|
});
|
||||||
})
|
}
|
||||||
},
|
});
|
||||||
})
|
|
||||||
|
|||||||
@@ -1,36 +1,27 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import {
|
import { fetchOrganizationById, organizationKeys } from "@app/hooks/api/organization/queries";
|
||||||
fetchOrganizationById,
|
import { fetchUserOrgPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||||
organizationKeys,
|
import { fetchOrgSubscription, subscriptionQueryKeys } from "@app/hooks/api/subscriptions/queries";
|
||||||
} from '@app/hooks/api/organization/queries'
|
|
||||||
import {
|
|
||||||
fetchUserOrgPermissions,
|
|
||||||
roleQueryKeys,
|
|
||||||
} from '@app/hooks/api/roles/queries'
|
|
||||||
import {
|
|
||||||
fetchOrgSubscription,
|
|
||||||
subscriptionQueryKeys,
|
|
||||||
} from '@app/hooks/api/subscriptions/queries'
|
|
||||||
|
|
||||||
// Route context to fill in organization's data like details, subscription etc
|
// Route context to fill in organization's data like details, subscription etc
|
||||||
export const Route = createFileRoute('/_authenticate/_inject-org-details')({
|
export const Route = createFileRoute("/_authenticate/_inject-org-details")({
|
||||||
beforeLoad: async ({ context }) => {
|
beforeLoad: async ({ context }) => {
|
||||||
const organizationId = context.organizationId!
|
const organizationId = context.organizationId!;
|
||||||
await context.queryClient.ensureQueryData({
|
await context.queryClient.ensureQueryData({
|
||||||
queryKey: organizationKeys.getOrgById(organizationId),
|
queryKey: organizationKeys.getOrgById(organizationId),
|
||||||
queryFn: () => fetchOrganizationById(organizationId),
|
queryFn: () => fetchOrganizationById(organizationId)
|
||||||
})
|
});
|
||||||
|
|
||||||
await context.queryClient.ensureQueryData({
|
await context.queryClient.ensureQueryData({
|
||||||
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId),
|
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId),
|
||||||
queryFn: () => fetchOrgSubscription(organizationId),
|
queryFn: () => fetchOrgSubscription(organizationId)
|
||||||
})
|
});
|
||||||
|
|
||||||
await context.queryClient.ensureQueryData({
|
await context.queryClient.ensureQueryData({
|
||||||
queryKey: roleQueryKeys.getUserOrgPermissions({ orgId: organizationId }),
|
queryKey: roleQueryKeys.getUserOrgPermissions({ orgId: organizationId }),
|
||||||
queryFn: () => fetchUserOrgPermissions({ orgId: organizationId }),
|
queryFn: () => fetchUserOrgPermissions({ orgId: organizationId })
|
||||||
})
|
});
|
||||||
return { organizationId }
|
return { organizationId };
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
|
|||||||
+2
-2
@@ -306,7 +306,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro
|
|||||||
className="h-10 w-full cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
className="h-10 w-full cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate({
|
navigate({
|
||||||
to: "/organization/memberships/$membershipId" as const,
|
to: "/organization/members/$membershipId" as const,
|
||||||
params: {
|
params: {
|
||||||
membershipId: orgMembershipId
|
membershipId: orgMembershipId
|
||||||
}
|
}
|
||||||
@@ -401,7 +401,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate({
|
navigate({
|
||||||
to: "/organization/memberships/$membershipId" as const,
|
to: "/organization/members/$membershipId" as const,
|
||||||
params: {
|
params: {
|
||||||
membershipId: orgMembershipId
|
membershipId: orgMembershipId
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -34,8 +34,7 @@ import { isCustomOrgRole } from "@app/helpers/roles";
|
|||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useDeleteOrgRole, useGetOrgRoles, useUpdateOrg } from "@app/hooks/api";
|
import { useDeleteOrgRole, useGetOrgRoles, useUpdateOrg } from "@app/hooks/api";
|
||||||
import { TOrgRole } from "@app/hooks/api/roles/types";
|
import { TOrgRole } from "@app/hooks/api/roles/types";
|
||||||
|
import { RoleModal } from "@app/pages/organization/RoleByIDPage/components/RoleModal";
|
||||||
import { RoleModal } from "../../../roles/$roleId/-components";
|
|
||||||
|
|
||||||
export const OrgRoleTable = () => {
|
export const OrgRoleTable = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { zodValidator } from '@tanstack/zod-adapter'
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from 'zod'
|
import { z } from "zod";
|
||||||
|
|
||||||
import { OrgAccessControlTabSections } from '@app/types/org'
|
import { OrgAccessControlTabSections } from "@app/types/org";
|
||||||
|
|
||||||
import { AccessManagementPage } from './AccessManagementPage'
|
import { AccessManagementPage } from "./AccessManagementPage";
|
||||||
|
|
||||||
const AccessControlPageQuerySchema = z.object({
|
const AccessControlPageQuerySchema = z.object({
|
||||||
selectedTab: z.string().catch(OrgAccessControlTabSections.Member),
|
selectedTab: z.string().catch(OrgAccessControlTabSections.Member),
|
||||||
action: z.string().catch(''),
|
action: z.string().catch("")
|
||||||
})
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/access-management',
|
"/_authenticate/_inject-org-details/organization/_layout/access-management"
|
||||||
)({
|
)({
|
||||||
component: AccessManagementPage,
|
component: AccessManagementPage,
|
||||||
validateSearch: zodValidator(AccessControlPageQuerySchema),
|
validateSearch: zodValidator(AccessControlPageQuerySchema)
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { AdminPage } from './AdminPage'
|
import { AdminPage } from "./AdminPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/admin',
|
"/_authenticate/_inject-org-details/organization/_layout/admin"
|
||||||
)({
|
)({
|
||||||
component: AdminPage,
|
component: AdminPage
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { AuditLogsPage } from './AuditLogsPage'
|
import { AuditLogsPage } from "./AuditLogsPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/audit-logs',
|
"/_authenticate/_inject-org-details/organization/_layout/audit-logs"
|
||||||
)({
|
)({
|
||||||
component: AuditLogsPage,
|
component: AuditLogsPage
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { BillingPage } from './BillingPage'
|
import { BillingPage } from "./BillingPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/billing',
|
"/_authenticate/_inject-org-details/organization/_layout/billing"
|
||||||
)({
|
)({
|
||||||
component: BillingPage,
|
component: BillingPage
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { CertManagerOverviewPage } from './CertManagerOverviewPage'
|
import { CertManagerOverviewPage } from "./CertManagerOverviewPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/cert-manager/overview',
|
"/_authenticate/_inject-org-details/organization/_layout/cert-manager/overview"
|
||||||
)({
|
)({
|
||||||
component: CertManagerOverviewPage,
|
component: CertManagerOverviewPage
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const Page = () => {
|
|||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
navigate({
|
navigate({
|
||||||
to: "/organization/members" as const,
|
to: "/organization/access-management" as const,
|
||||||
search: {
|
search: {
|
||||||
selectedTab: TabSections.Groups
|
selectedTab: TabSections.Groups
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ const Page = () => {
|
|||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate({
|
navigate({
|
||||||
to: "/organization/members" as const,
|
to: "/organization/access-management" as const,
|
||||||
search: {
|
search: {
|
||||||
selectedTab: TabSections.Groups
|
selectedTab: TabSections.Groups
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { GroupDetailsByIDPage } from './GroupDetailsByIDPage'
|
import { GroupDetailsByIDPage } from "./GroupDetailsByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/groups/$groupId',
|
"/_authenticate/_inject-org-details/organization/_layout/groups/$groupId"
|
||||||
)({
|
)({
|
||||||
component: GroupDetailsByIDPage,
|
component: GroupDetailsByIDPage
|
||||||
})
|
});
|
||||||
|
|||||||
+5
-3
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -32,6 +31,9 @@ import { Identity } from "@app/hooks/api/identities/types";
|
|||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
import { OrgAccessControlTabSections } from "@app/types/org";
|
import { OrgAccessControlTabSections } from "@app/types/org";
|
||||||
|
|
||||||
|
import { IdentityAuthMethodModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal";
|
||||||
|
import { IdentityModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal";
|
||||||
|
import { IdentityUniversalAuthClientSecretModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthClientSecretModal";
|
||||||
import {
|
import {
|
||||||
IdentityAuthenticationSection,
|
IdentityAuthenticationSection,
|
||||||
IdentityClientSecretModal,
|
IdentityClientSecretModal,
|
||||||
@@ -86,7 +88,7 @@ const Page = () => {
|
|||||||
|
|
||||||
handlePopUpClose("deleteIdentity");
|
handlePopUpClose("deleteIdentity");
|
||||||
navigate({
|
navigate({
|
||||||
to: "/organization/members",
|
to: "/organization/access-management",
|
||||||
search: {
|
search: {
|
||||||
selectedTab: OrgAccessControlTabSections.Identities
|
selectedTab: OrgAccessControlTabSections.Identities
|
||||||
}
|
}
|
||||||
@@ -170,7 +172,7 @@ const Page = () => {
|
|||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate({
|
navigate({
|
||||||
to: "/organization/members",
|
to: "/organization/access-management",
|
||||||
search: {
|
search: {
|
||||||
selectedTab: OrgAccessControlTabSections.Identities
|
selectedTab: OrgAccessControlTabSections.Identities
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -59,7 +59,10 @@ export const IdentityProjectRow = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isAccessible) {
|
if (isAccessible) {
|
||||||
navigate({
|
navigate({
|
||||||
to: `/${project?.type}/${project.id}/access` as const,
|
to: `/${project?.type}/$projectId/access-management` as const,
|
||||||
|
params: {
|
||||||
|
projectId: project.id
|
||||||
|
},
|
||||||
search: {
|
search: {
|
||||||
selectedTab: TabSections.Identities
|
selectedTab: TabSections.Identities
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { IdentityDetailsByIDPage } from './IdentityDetailsByIDPage'
|
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/identities/$identityId',
|
"/_authenticate/_inject-org-details/organization/_layout/identities/$identityId"
|
||||||
)({
|
)({
|
||||||
component: IdentityDetailsByIDPage,
|
component: IdentityDetailsByIDPage
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { KmsOverviewPage } from './KmsOverviewPage'
|
import { KmsOverviewPage } from "./KmsOverviewPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/kms/overview',
|
"/_authenticate/_inject-org-details/organization/_layout/kms/overview"
|
||||||
)({
|
)({
|
||||||
component: KmsOverviewPage,
|
component: KmsOverviewPage
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { NoOrgPage } from './NoOrgPage'
|
|
||||||
|
import { NoOrgPage } from "./NoOrgPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/none',
|
"/_authenticate/_inject-org-details/organization/_layout/none"
|
||||||
)({
|
)({
|
||||||
component: NoOrgPage,
|
component: NoOrgPage
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const Page = () => {
|
|||||||
|
|
||||||
handlePopUpClose("deleteOrgRole");
|
handlePopUpClose("deleteOrgRole");
|
||||||
navigate({
|
navigate({
|
||||||
to: "/organization/members" as const,
|
to: "/organization/access-management" as const,
|
||||||
search: {
|
search: {
|
||||||
selectedTab: OrgAccessControlTabSections.Roles
|
selectedTab: OrgAccessControlTabSections.Roles
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ export const Page = () => {
|
|||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate({
|
navigate({
|
||||||
to: "/organization/members" as const,
|
to: "/organization/access-management" as const,
|
||||||
search: {
|
search: {
|
||||||
selectedTab: OrgAccessControlTabSections.Roles
|
selectedTab: OrgAccessControlTabSections.Roles
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { RoleByIDPage } from './RoleByIDPage'
|
import { RoleByIDPage } from "./RoleByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/roles/$roleId',
|
"/_authenticate/_inject-org-details/organization/_layout/roles/$roleId"
|
||||||
)({
|
)({
|
||||||
component: RoleByIDPage,
|
component: RoleByIDPage
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { SecretManagerOverviewPage } from './SecretManagerOverviewPage'
|
import { SecretManagerOverviewPage } from "./SecretManagerOverviewPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/secret-manager/overview',
|
"/_authenticate/_inject-org-details/organization/_layout/secret-manager/overview"
|
||||||
)({
|
)({
|
||||||
component: SecretManagerOverviewPage,
|
component: SecretManagerOverviewPage
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useSearch } from "@tanstack/react-router";
|
|||||||
|
|
||||||
import { OrgPermissionCan } from "@app/components/permissions";
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, NoticeBanner } from "@app/components/v2";
|
import { Button, NoticeBanner } from "@app/components/v2";
|
||||||
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
@@ -22,7 +23,7 @@ import { SecretScanningLogsTable } from "./components";
|
|||||||
export const SecretScanningPage = withPermission(
|
export const SecretScanningPage = withPermission(
|
||||||
() => {
|
() => {
|
||||||
const queryParams = useSearch({
|
const queryParams = useSearch({
|
||||||
from: "/_authenticate/_ctx-org-details/organization/_layout-org/secret-scanning/"
|
from: ROUTE_PATHS.Organization.SecretScanning.id
|
||||||
});
|
});
|
||||||
const { config } = useServerConfig();
|
const { config } = useServerConfig();
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { zodValidator } from '@tanstack/zod-adapter'
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from 'zod'
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretScanningPage } from './SecretScanningPage'
|
import { SecretScanningPage } from "./SecretScanningPage";
|
||||||
|
|
||||||
const SecretScanningQueryParams = z.object({
|
const SecretScanningQueryParams = z.object({
|
||||||
state: z.string().catch(''),
|
state: z.string().catch(""),
|
||||||
installation_id: z.string().catch(''),
|
installation_id: z.string().catch("")
|
||||||
})
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/secret-scanning',
|
"/_authenticate/_inject-org-details/organization/_layout/secret-scanning"
|
||||||
)({
|
)({
|
||||||
component: SecretScanningPage,
|
component: SecretScanningPage,
|
||||||
validateSearch: zodValidator(SecretScanningQueryParams),
|
validateSearch: zodValidator(SecretScanningQueryParams)
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { SecretSharingPage } from './SecretSharingPage'
|
import { SecretSharingPage } from "./SecretSharingPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/secret-sharing',
|
"/_authenticate/_inject-org-details/organization/_layout/secret-sharing"
|
||||||
)({
|
)({
|
||||||
component: SecretSharingPage,
|
component: SecretSharingPage
|
||||||
})
|
});
|
||||||
|
|||||||
+1
-1
@@ -279,7 +279,7 @@ export const LDAPGroupMapModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }:
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate({
|
navigate({
|
||||||
to: "/organization/members"
|
to: "/organization/access-management"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import { Button, DeleteActionModal } from "@app/components/v2";
|
|||||||
import { useOrganization, useOrgPermission } from "@app/context";
|
import { useOrganization, useOrgPermission } from "@app/context";
|
||||||
import { useDeleteOrgById } from "@app/hooks/api";
|
import { useDeleteOrgById } from "@app/hooks/api";
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
import { navigateUserToOrg } from "@app/routes/_restrict-login-signup/login/-components/Login.utils";
|
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
|
||||||
|
|
||||||
export const OrgDeleteSection = () => {
|
export const OrgDeleteSection = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|||||||
+2
-1
@@ -3,6 +3,7 @@ import { useSearch } from "@tanstack/react-router";
|
|||||||
|
|
||||||
import { OrgPermissionCan } from "@app/components/permissions";
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||||
|
|
||||||
import { AuditLogStreamsTab } from "../AuditLogStreamTab";
|
import { AuditLogStreamsTab } from "../AuditLogStreamTab";
|
||||||
@@ -24,7 +25,7 @@ const tabs = [
|
|||||||
];
|
];
|
||||||
export const OrgTabGroup = () => {
|
export const OrgTabGroup = () => {
|
||||||
const search = useSearch({
|
const search = useSearch({
|
||||||
from: "/_authenticate/_ctx-org-details/organization/_layout-org/settings/"
|
from: ROUTE_PATHS.Organization.SettingsPage.id
|
||||||
});
|
});
|
||||||
const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key);
|
const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key);
|
||||||
|
|
||||||
|
|||||||
+10
-10
@@ -6,22 +6,22 @@ import { twMerge } from "tailwind-merge";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { GeneralPermissionPolicies } from "@app/components/permissions/ProjectRolePermissionsSection/components/GeneralPermissionPolicies";
|
|
||||||
import { NewPermissionRule } from "@app/components/permissions/ProjectRolePermissionsSection/components/NewPermissionRule";
|
|
||||||
import { PermissionEmptyState } from "@app/components/permissions/ProjectRolePermissionsSection/PermissionEmptyState";
|
|
||||||
import {
|
|
||||||
formRolePermission2API,
|
|
||||||
PROJECT_PERMISSION_OBJECT,
|
|
||||||
projectRoleFormSchema,
|
|
||||||
rolePermission2Form
|
|
||||||
} from "@app/components/permissions/ProjectRolePermissionsSection/ProjectRoleModifySection.utils";
|
|
||||||
import { renderConditionalComponents } from "@app/components/permissions/ProjectRolePermissionsSection/RolePermissionsSection";
|
|
||||||
import { Button, FormControl, Input, Modal, ModalContent, ModalTrigger } from "@app/components/v2";
|
import { Button, FormControl, Input, Modal, ModalContent, ModalTrigger } from "@app/components/v2";
|
||||||
import { ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionSub } from "@app/context";
|
||||||
import { isCustomProjectRole } from "@app/helpers/roles";
|
import { isCustomProjectRole } from "@app/helpers/roles";
|
||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { TProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates";
|
import { TProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates";
|
||||||
import { slugSchema } from "@app/lib/schemas";
|
import { slugSchema } from "@app/lib/schemas";
|
||||||
|
import { GeneralPermissionPolicies } from "@app/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies";
|
||||||
|
import { NewPermissionRule } from "@app/pages/project/RoleDetailsBySlugPage/components/NewPermissionRule";
|
||||||
|
import { PermissionEmptyState } from "@app/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState";
|
||||||
|
import {
|
||||||
|
formRolePermission2API,
|
||||||
|
PROJECT_PERMISSION_OBJECT,
|
||||||
|
projectRoleFormSchema,
|
||||||
|
rolePermission2Form
|
||||||
|
} from "@app/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils";
|
||||||
|
import { renderConditionalComponents } from "@app/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
projectTemplate: TProjectTemplate;
|
projectTemplate: TProjectTemplate;
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { zodValidator } from '@tanstack/zod-adapter'
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from 'zod'
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SettingsPage } from './SettingsPage'
|
import { SettingsPage } from "./SettingsPage";
|
||||||
|
|
||||||
const SettingsPageQueryParams = z.object({
|
const SettingsPageQueryParams = z.object({
|
||||||
selectedTab: z.string().catch(''),
|
selectedTab: z.string().catch("")
|
||||||
})
|
});
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/settings',
|
"/_authenticate/_inject-org-details/organization/_layout/settings"
|
||||||
)({
|
)({
|
||||||
component: SettingsPage,
|
component: SettingsPage,
|
||||||
validateSearch: zodValidator(SettingsPageQueryParams),
|
validateSearch: zodValidator(SettingsPageQueryParams)
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ const Page = withPermission(
|
|||||||
|
|
||||||
handlePopUpClose("removeMember");
|
handlePopUpClose("removeMember");
|
||||||
navigate({
|
navigate({
|
||||||
to: "/organization/members" as const,
|
to: "/organization/access-management" as const,
|
||||||
search: {
|
search: {
|
||||||
selectedTab: OrgAccessControlTabSections.Member
|
selectedTab: OrgAccessControlTabSections.Member
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ const Page = withPermission(
|
|||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate({
|
navigate({
|
||||||
to: "/organization/members" as const,
|
to: "/organization/access-management" as const,
|
||||||
search: {
|
search: {
|
||||||
selectedTab: OrgAccessControlTabSections.Member
|
selectedTab: OrgAccessControlTabSections.Member
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -6,8 +6,7 @@ import { IconButton, Tooltip } from "@app/components/v2";
|
|||||||
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||||
import { withPermission } from "@app/hoc";
|
import { withPermission } from "@app/hoc";
|
||||||
import { OrgUser } from "@app/hooks/api/types";
|
import { OrgUser } from "@app/hooks/api/types";
|
||||||
|
import { LogsSection } from "@app/pages/organization/AuditLogsPage/components";
|
||||||
import { LogsSection } from "../../../../audit-logs/-components";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
orgMembership: OrgUser;
|
orgMembership: OrgUser;
|
||||||
|
|||||||
+1
-1
@@ -52,7 +52,7 @@ export const UserProjectRow = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isAccessible) {
|
if (isAccessible) {
|
||||||
navigate({
|
navigate({
|
||||||
to: `/${project.type}/$projectId/access` as const,
|
to: `/${project.type}/$projectId/access-management` as const,
|
||||||
params: {
|
params: {
|
||||||
projectId: project.id
|
projectId: project.id
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { UserDetailsByIDPage } from './UserDetailsByIDPage'
|
import { UserDetailsByIDPage } from "./UserDetailsByIDPage";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/organization/_layout/members/$membershipId',
|
"/_authenticate/_inject-org-details/organization/_layout/members/$membershipId"
|
||||||
)({
|
)({
|
||||||
component: UserDetailsByIDPage,
|
component: UserDetailsByIDPage
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { OrganizationLayout } from '@app/layouts/OrganizationLayout'
|
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute("/_authenticate/_inject-org-details/organization/_layout")({
|
||||||
'/_authenticate/_inject-org-details/organization/_layout',
|
component: OrganizationLayout
|
||||||
)({
|
});
|
||||||
component: OrganizationLayout,
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const Page = withProjectPermission(
|
|||||||
|
|
||||||
const updateSelectedTab = (tab: string) => {
|
const updateSelectedTab = (tab: string) => {
|
||||||
navigate({
|
navigate({
|
||||||
to: `/${currentWorkspace.type}/$projectId/access` as const,
|
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
||||||
search: (prev) => ({ ...prev, selectedTab: tab }),
|
search: (prev) => ({ ...prev, selectedTab: tab }),
|
||||||
params: {
|
params: {
|
||||||
projectId: currentWorkspace.id
|
projectId: currentWorkspace.id
|
||||||
|
|||||||
@@ -15,4 +15,3 @@ export const GroupsTab = () => {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -147,7 +147,7 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
All groups in your organization have already been added to this project.
|
All groups in your organization have already been added to this project.
|
||||||
</div>
|
</div>
|
||||||
<Link to={"/organization/members" as const}>
|
<Link to={"/organization/access-management" as const}>
|
||||||
<Button variant="outline_bg">Create a new group</Button>
|
<Button variant="outline_bg">Create a new group</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
export { GroupsSection } from "./GroupsSection";
|
export { GroupsSection } from "./GroupsSection";
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
export { GroupsSection } from "./GroupsSection";
|
export { GroupsSection } from "./GroupsSection";
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export { GroupsTab } from "./GroupsTab";
|
export { GroupsTab } from "./GroupsTab";
|
||||||
|
|||||||
+1
-1
@@ -182,7 +182,7 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
All identities in your organization have already been added to this project.
|
All identities in your organization have already been added to this project.
|
||||||
</div>
|
</div>
|
||||||
<Link to={"/organization/members" as const}>
|
<Link to={"/organization/access-management" as const}>
|
||||||
<Button isDisabled={isRolesLoading} isLoading={isRolesLoading} variant="outline_bg">
|
<Button isDisabled={isRolesLoading} isLoading={isRolesLoading} variant="outline_bg">
|
||||||
Create a new identity
|
Create a new identity
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
+1
-1
@@ -211,7 +211,7 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
<div>All the users in your organization are already invited.</div>
|
<div>All the users in your organization are already invited.</div>
|
||||||
<Link to={"/organization/members" as const}>
|
<Link to={"/organization/access-management" as const}>
|
||||||
<Button variant="outline_bg">Add users to organization</Button>
|
<Button variant="outline_bg">Add users to organization</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@a
|
|||||||
import { usePopUp } from "@app/hooks";
|
import { usePopUp } from "@app/hooks";
|
||||||
import { useDeleteProjectRole, useGetProjectRoles } from "@app/hooks/api";
|
import { useDeleteProjectRole, useGetProjectRoles } from "@app/hooks/api";
|
||||||
import { TProjectRole } from "@app/hooks/api/roles/types";
|
import { TProjectRole } from "@app/hooks/api/roles/types";
|
||||||
import { RoleModal } from "@app/routes/_authenticate/_ctx-org-details/organization/_layout-org/roles/$roleId/-components";
|
import { RoleModal } from "@app/pages/organization/RoleByIDPage/components";
|
||||||
|
|
||||||
export const ProjectRoleList = () => {
|
export const ProjectRoleList = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ type DeleteModalData = { name: string; id: string };
|
|||||||
export const ServiceTokenSection = withProjectPermission(
|
export const ServiceTokenSection = withProjectPermission(
|
||||||
() => {
|
() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const deleteServiceToken = useDeleteServiceToken();
|
const deleteServiceToken = useDeleteServiceToken();
|
||||||
|
|
||||||
const { popUp, handlePopUpToggle, handlePopUpClose, handlePopUpOpen } = usePopUp([
|
const { popUp, handlePopUpToggle, handlePopUpClose, handlePopUpOpen } = usePopUp([
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { zodValidator } from '@tanstack/zod-adapter'
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from 'zod'
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectAccessControlTabs } from '@app/types/project'
|
import { ProjectAccessControlTabs } from "@app/types/project";
|
||||||
|
|
||||||
import { AccessControlPage } from './AccessControlPage'
|
import { AccessControlPage } from "./AccessControlPage";
|
||||||
|
|
||||||
const AccessControlPageQuerySchema = z.object({
|
const AccessControlPageQuerySchema = z.object({
|
||||||
selectedTab: z
|
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member)
|
||||||
.nativeEnum(ProjectAccessControlTabs)
|
});
|
||||||
.catch(ProjectAccessControlTabs.Member),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
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)
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { zodValidator } from '@tanstack/zod-adapter'
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from 'zod'
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectAccessControlTabs } from '@app/types/project'
|
import { ProjectAccessControlTabs } from "@app/types/project";
|
||||||
|
|
||||||
import { AccessControlPage } from './AccessControlPage'
|
import { AccessControlPage } from "./AccessControlPage";
|
||||||
|
|
||||||
const AccessControlPageQuerySchema = z.object({
|
const AccessControlPageQuerySchema = z.object({
|
||||||
selectedTab: z
|
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member)
|
||||||
.nativeEnum(ProjectAccessControlTabs)
|
});
|
||||||
.catch(ProjectAccessControlTabs.Member),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
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)
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { zodValidator } from '@tanstack/zod-adapter'
|
import { zodValidator } from "@tanstack/zod-adapter";
|
||||||
import { z } from 'zod'
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectAccessControlTabs } from '@app/types/project'
|
import { ProjectAccessControlTabs } from "@app/types/project";
|
||||||
|
|
||||||
import { AccessControlPage } from './AccessControlPage'
|
import { AccessControlPage } from "./AccessControlPage";
|
||||||
|
|
||||||
const AccessControlPageQuerySchema = z.object({
|
const AccessControlPageQuerySchema = z.object({
|
||||||
selectedTab: z
|
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member)
|
||||||
.nativeEnum(ProjectAccessControlTabs)
|
});
|
||||||
.catch(ProjectAccessControlTabs.Member),
|
|
||||||
})
|
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
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)
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const Page = () => {
|
|||||||
});
|
});
|
||||||
handlePopUpClose("deleteIdentity");
|
handlePopUpClose("deleteIdentity");
|
||||||
navigate({
|
navigate({
|
||||||
to: `/${currentWorkspace.type}/$projectId/access` as const,
|
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
||||||
params: {
|
params: {
|
||||||
projectId: workspaceId
|
projectId: workspaceId
|
||||||
},
|
},
|
||||||
@@ -90,7 +90,7 @@ const Page = () => {
|
|||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate({
|
navigate({
|
||||||
to: `/${currentWorkspace.type}/$projectId/access` as const,
|
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
||||||
params: {
|
params: {
|
||||||
projectId: workspaceId
|
projectId: workspaceId
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } 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
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } 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
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } 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
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const Page = () => {
|
|||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
navigate({
|
navigate({
|
||||||
to: `/${currentWorkspace.type}/$projectId/access` as const,
|
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
||||||
params: {
|
params: {
|
||||||
projectId: currentWorkspace.id
|
projectId: currentWorkspace.id
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ export const Page = () => {
|
|||||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate({
|
navigate({
|
||||||
to: `/${currentWorkspace.type}/$projectId/access` as const,
|
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
||||||
params: {
|
params: {
|
||||||
projectId: currentWorkspace.id
|
projectId: currentWorkspace.id
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -15,16 +15,6 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { TtlFormLabel } from "@app/components/features";
|
import { TtlFormLabel } from "@app/components/features";
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { GeneralPermissionPolicies } from "@app/components/permissions/ProjectRolePermissionsSection/components/GeneralPermissionPolicies";
|
|
||||||
import { PermissionEmptyState } from "@app/components/permissions/ProjectRolePermissionsSection/PermissionEmptyState";
|
|
||||||
import {
|
|
||||||
formRolePermission2API,
|
|
||||||
isConditionalSubjects,
|
|
||||||
PROJECT_PERMISSION_OBJECT,
|
|
||||||
projectRoleFormSchema,
|
|
||||||
rolePermission2Form
|
|
||||||
} from "@app/components/permissions/ProjectRolePermissionsSection/ProjectRoleModifySection.utils";
|
|
||||||
import { renderConditionalComponents } from "@app/components/permissions/ProjectRolePermissionsSection/RolePermissionsSection";
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -52,6 +42,16 @@ import {
|
|||||||
useUpdateProjectUserAdditionalPrivilege
|
useUpdateProjectUserAdditionalPrivilege
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/hooks/api/projectUserAdditionalPrivilege/types";
|
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/hooks/api/projectUserAdditionalPrivilege/types";
|
||||||
|
import { GeneralPermissionPolicies } from "@app/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies";
|
||||||
|
import { PermissionEmptyState } from "@app/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState";
|
||||||
|
import {
|
||||||
|
formRolePermission2API,
|
||||||
|
isConditionalSubjects,
|
||||||
|
PROJECT_PERMISSION_OBJECT,
|
||||||
|
projectRoleFormSchema,
|
||||||
|
rolePermission2Form
|
||||||
|
} from "@app/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils";
|
||||||
|
import { renderConditionalComponents } from "@app/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
privilegeId?: string;
|
privilegeId?: string;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user