feat: resolved almost all ts issues

This commit is contained in:
=
2025-01-06 14:55:11 +05:30
parent ef7b81734a
commit d77c26fa38
141 changed files with 3212 additions and 469 deletions
@@ -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";
+24
View File
@@ -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
@@ -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>
);
};
@@ -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"
});
}
}
});
+14
View File
@@ -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")({
@@ -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
}) });
@@ -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"
@@ -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"
@@ -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
}
}) })
} }
> >
@@ -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
@@ -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
}
}); });
} }
@@ -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
}) });
@@ -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
}) });
+13 -17
View File
@@ -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 };
}, }
}) });
@@ -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
} }
@@ -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
}) });
@@ -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
} }
@@ -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
}) });
@@ -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"
}) })
} }
> >
@@ -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();
@@ -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);
@@ -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
} }
@@ -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;
@@ -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>
); );
}; };
@@ -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>
@@ -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>
@@ -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>
@@ -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,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
} }
@@ -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;
@@ -1,9 +1,9 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from "@tanstack/react-router";
import { MemberDetailsByIDPage } from './MemberDetailsByIdPage' import { MemberDetailsByIDPage } from "./MemberDetailsByIdPage";
export const Route = createFileRoute( export const Route = createFileRoute(
'/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/members/$membershipId', "/_authenticate/_inject-org-details/cert-manager/$projectId/_cert-manager-layout/members/$membershipId"
)({ )({
component: MemberDetailsByIDPage, component: MemberDetailsByIDPage
}) });
@@ -1,9 +1,9 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from "@tanstack/react-router";
import { MemberDetailsByIDPage } from './MemberDetailsByIdPage' import { MemberDetailsByIDPage } from "./MemberDetailsByIdPage";
export const Route = createFileRoute( export const Route = createFileRoute(
'/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/members/$membershipId', "/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/members/$membershipId"
)({ )({
component: MemberDetailsByIDPage, component: MemberDetailsByIDPage
}) });
@@ -1,9 +1,9 @@
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from "@tanstack/react-router";
import { MemberDetailsByIDPage } from './MemberDetailsByIdPage' import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
export const Route = createFileRoute( export const Route = createFileRoute(
'/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/members/$membershipId', "/_authenticate/_inject-org-details/secret-manager/$projectId/_secret-manager-layout/members/$membershipId"
)({ )({
component: MemberDetailsByIDPage, component: MemberDetailsByIDPage
}) });
@@ -29,7 +29,7 @@ const Page = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const roleSlug = useParams({ const roleSlug = useParams({
strict: false, strict: false,
select: (el) => el.roleSlug select: (el) => el.roleSlug as string
}); });
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const projectId = currentWorkspace?.id || ""; const projectId = currentWorkspace?.id || "";
@@ -58,7 +58,7 @@ const Page = () => {
}); });
handlePopUpClose("deleteRole"); handlePopUpClose("deleteRole");
navigate({ navigate({
to: `/${currentWorkspace?.type}/$projectId/access` as const, to: `/${currentWorkspace?.type}/$projectId/access-management` as const,
params: { params: {
projectId projectId
}, },
@@ -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 projectId
}, },

Some files were not shown because too many files have changed in this diff Show More