mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: resolved almost all ts issues
This commit is contained in:
@@ -4,6 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, useParams } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { useOrganization, useWorkspace } from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
@@ -59,7 +60,7 @@ export default function NavHeader({
|
||||
const [isHoveringCopyButton, setIsHoveringCopyButton] = useState(false);
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { hasSecretReference,SecretReferenceTree } from "./SecretReferenceDetails";
|
||||
export { hasSecretReference, SecretReferenceTree } from "./SecretReferenceDetails";
|
||||
|
||||
@@ -6,12 +6,36 @@ const setRoute = <TFull extends FileRouteTypes["fullPaths"], TId extends FileRou
|
||||
) => ({ full, id }) as const;
|
||||
|
||||
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(
|
||||
"/login/provider/success",
|
||||
"/_restrict-login-signup/login/provider/success"
|
||||
),
|
||||
SignUpSsoPage: setRoute("/signup/sso", "/_restrict-login-signup/signup/sso"),
|
||||
PasswordResetPage: setRoute("/password-reset", "/_restrict-login-signup/password-reset"),
|
||||
ViewSharedSecretByIDPage: setRoute("/shared/secret/$secretId", "/shared/secret/$secretId"),
|
||||
OrgGroupDetailsByIDPage: setRoute(
|
||||
"/organization/groups/$groupId",
|
||||
"/_authenticate/_inject-org-details/organization/_layout/groups/$groupId"
|
||||
|
||||
@@ -13,7 +13,7 @@ import { OrgPermissionSet } from "./types";
|
||||
|
||||
export const useOrgPermission = () => {
|
||||
const organizationId = useRouteContext({
|
||||
from: "/_authenticate/_ctx-org-details",
|
||||
from: "/_authenticate/_inject-org-details",
|
||||
select: (el) => el.organizationId
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { fetchOrganizationById, organizationKeys } from "@app/hooks/api/organiza
|
||||
|
||||
export const useOrganization = () => {
|
||||
const organizationId = useRouteContext({
|
||||
from: "/_authenticate/_ctx-org-details",
|
||||
from: "/_authenticate/_inject-org-details",
|
||||
select: (el) => el.organizationId
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { fetchOrgSubscription, subscriptionQueryKeys } from "@app/hooks/api/subs
|
||||
|
||||
export const useSubscription = () => {
|
||||
const organizationId = useRouteContext({
|
||||
from: "/_authenticate/_ctx-org-details",
|
||||
from: "/_authenticate/_inject-org-details",
|
||||
select: (el) => el.organizationId
|
||||
});
|
||||
|
||||
|
||||
89
frontend-v2/src/layouts/AdminLayout/AdminLayout.tsx
Normal file
89
frontend-v2/src/layouts/AdminLayout/AdminLayout.tsx
Normal file
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
1
frontend-v2/src/layouts/AdminLayout/index.tsx
Normal file
1
frontend-v2/src/layouts/AdminLayout/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { AdminLayout } from "./AdminLayout";
|
||||
@@ -92,7 +92,7 @@ export const OrganizationLayout = () => {
|
||||
</MenuItem>
|
||||
)}
|
||||
</Link>
|
||||
<Link to="/organization/members">
|
||||
<Link to="/organization/access-management">
|
||||
{({ isActive }) => (
|
||||
<MenuItem isSelected={isActive} icon="system-outline-96-groups">
|
||||
Access Control
|
||||
|
||||
@@ -62,7 +62,7 @@ export const SidebarFooter = () => {
|
||||
{(window.location.origin.includes("https://app.infisical.com") ||
|
||||
window.location.origin.includes("https://gamma.infisical.com")) && <WishForm />}
|
||||
<Link
|
||||
to="/organization/members"
|
||||
to="/organization/access-management"
|
||||
search={{
|
||||
action: "invite"
|
||||
}}
|
||||
|
||||
@@ -134,7 +134,7 @@ export const ProjectLayout = () => {
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
to={`/${currentWorkspace.type}/$projectId/access` as const}
|
||||
to={`/${currentWorkspace.type}/$projectId/access-management` as const}
|
||||
params={{
|
||||
projectId: currentWorkspace.id
|
||||
}}
|
||||
|
||||
@@ -42,7 +42,7 @@ export const SidebarHeader = ({ onChangeOrg }: Prop) => {
|
||||
|
||||
return (
|
||||
<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">
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
</div>
|
||||
|
||||
357
frontend-v2/src/pages/admin/OverviewPage/OverviewPage.tsx
Normal file
357
frontend-v2/src/pages/admin/OverviewPage/OverviewPage.tsx
Normal file
@@ -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>
|
||||
);
|
||||
};
|
||||
7
frontend-v2/src/pages/admin/OverviewPage/route.tsx
Normal file
7
frontend-v2/src/pages/admin/OverviewPage/route.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { OverviewPage } from "./OverviewPage";
|
||||
|
||||
export const Route = createFileRoute("/_authenticate/admin/_admin-layout/")({
|
||||
component: OverviewPage
|
||||
});
|
||||
255
frontend-v2/src/pages/admin/SignUpPage/SignUpPage.tsx
Normal file
255
frontend-v2/src/pages/admin/SignUpPage/SignUpPage.tsx
Normal file
@@ -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";
|
||||
14
frontend-v2/src/pages/admin/SignUpPage/route.tsx
Normal file
14
frontend-v2/src/pages/admin/SignUpPage/route.tsx
Normal file
@@ -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
frontend-v2/src/pages/admin/layout.tsx
Normal file
14
frontend-v2/src/pages/admin/layout.tsx
Normal 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";
|
||||
|
||||
export const Route = createFileRoute("/_authenticate/cli-redirect")({
|
||||
export const Route = createFileRoute("/_restrict-login-signup/cli-redirect")({
|
||||
component: CliRedirectPage
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { EmailNotVerifiedPage } from "./EmailNotVerifiedPage";
|
||||
|
||||
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/')({
|
||||
component: LoginPage,
|
||||
})
|
||||
export const Route = createFileRoute("/_restrict-login-signup/login/")({
|
||||
component: LoginPage
|
||||
});
|
||||
|
||||
@@ -4,11 +4,13 @@ import { useTranslation } from "react-i18next";
|
||||
import { Link, useSearch } from "@tanstack/react-router";
|
||||
import { jwtDecode } from "jwt-decode";
|
||||
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
|
||||
import { PasswordStep } from "../LoginPage/components";
|
||||
|
||||
export const LoginSsoPage = () => {
|
||||
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 [step, setStep] = useState(0);
|
||||
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(
|
||||
'/_restrict-login-signup/login/provider/error',
|
||||
)({
|
||||
component: LoginProviderErrorPage,
|
||||
})
|
||||
export const Route = createFileRoute("/_restrict-login-signup/login/provider/error")({
|
||||
component: LoginProviderErrorPage
|
||||
});
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { zodValidator } from '@tanstack/zod-adapter'
|
||||
import { z } from 'zod'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { LoginProviderSuccessPage } from './ProviderSuccessPage'
|
||||
import { LoginProviderSuccessPage } from "./ProviderSuccessPage";
|
||||
|
||||
const LoginProviderSuccessQuerySchema = z.object({
|
||||
token: z.string(),
|
||||
})
|
||||
token: z.string()
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_restrict-login-signup/login/provider/success',
|
||||
)({
|
||||
export const Route = createFileRoute("/_restrict-login-signup/login/provider/success")({
|
||||
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(
|
||||
'/_restrict-login-signup/login/select-organization',
|
||||
)({
|
||||
component: SelectOrganizationPage,
|
||||
})
|
||||
export const Route = createFileRoute("/_restrict-login-signup/login/select-organization")({
|
||||
component: SelectOrganizationPage
|
||||
});
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { zodValidator } from '@tanstack/zod-adapter'
|
||||
import { z } from 'zod'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SignupSsoPage } from './SignUpSsoPage'
|
||||
import { SignupSsoPage } from "./SignUpSsoPage";
|
||||
|
||||
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,
|
||||
validateSearch: zodValidator(SignupSSOPageQueryParamsSchema),
|
||||
})
|
||||
validateSearch: zodValidator(SignupSSOPageQueryParamsSchema)
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { VerifyEmailPage } from "./VerifyEmailPage";
|
||||
|
||||
export const Route = createFileRoute("/_restrict-login-signup/verify-email")({
|
||||
|
||||
@@ -63,7 +63,12 @@ const Page = () => {
|
||||
});
|
||||
|
||||
handlePopUpClose("deleteCa");
|
||||
navigate({ to: `/${ProjectType.CertificateManager}/${projectId}/certificates` as const });
|
||||
navigate({
|
||||
to: `/${ProjectType.CertificateManager}/$projectId/overview` as const,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
createNotification({
|
||||
text: "Failed to delete CA",
|
||||
@@ -82,7 +87,10 @@ const Page = () => {
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: `/${ProjectType.CertificateManager}/${projectId}/certificates` as const
|
||||
to: `/${ProjectType.CertificateManager}/${projectId}/overview` as const,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
})
|
||||
}
|
||||
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(
|
||||
'/_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"
|
||||
});
|
||||
handlePopUpToggle("installCaCert", false);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
createNotification({
|
||||
text: "Failed to install certificate for CA",
|
||||
type: "error"
|
||||
|
||||
@@ -126,7 +126,7 @@ export const InternalCaInstallForm = ({ caId, handlePopUpToggle }: Props) => {
|
||||
type: "success"
|
||||
});
|
||||
handlePopUpToggle("installCaCert", false);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
createNotification({
|
||||
text: "Failed to install certificate for CA",
|
||||
type: "error"
|
||||
|
||||
@@ -78,7 +78,11 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
|
||||
key={`ca-${ca.id}`}
|
||||
onClick={() =>
|
||||
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}
|
||||
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 }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
@@ -441,7 +441,7 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
|
||||
errorText={error?.message}
|
||||
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 }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
|
||||
@@ -76,14 +76,18 @@ export const PkiCollectionModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
});
|
||||
} else {
|
||||
// create
|
||||
const { id: createdId } = await createPkiCollection({
|
||||
const { id: collectionId } = await createPkiCollection({
|
||||
name,
|
||||
description,
|
||||
projectId
|
||||
});
|
||||
|
||||
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}`}
|
||||
onClick={() =>
|
||||
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(
|
||||
'/_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");
|
||||
navigate({
|
||||
to: `/${ProjectType.CertificateManager}/${projectId}/certificates` as const
|
||||
to: `/${ProjectType.CertificateManager}/$projectId/overview` as const,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
createNotification({
|
||||
@@ -78,7 +81,10 @@ export const PkiCollectionPage = () => {
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={() => {
|
||||
navigate({
|
||||
to: `/${ProjectType.CertificateManager}/${projectId}/certificates` as const
|
||||
to: `/${ProjectType.CertificateManager}/$projectId/overview` as const,
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
});
|
||||
}}
|
||||
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(
|
||||
'/_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 { EnvironmentSection } from "./EnvironmentSection";
|
||||
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(
|
||||
'/_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(
|
||||
'/_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 { EnvironmentSection } from "./EnvironmentSection";
|
||||
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(
|
||||
'/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/settings',
|
||||
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout/settings"
|
||||
)({
|
||||
component: SettingsPage,
|
||||
})
|
||||
component: SettingsPage
|
||||
});
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { workspaceKeys } from '@app/hooks/api'
|
||||
import {
|
||||
fetchUserProjectPermissions,
|
||||
roleQueryKeys,
|
||||
} from '@app/hooks/api/roles/queries'
|
||||
import { fetchWorkspaceById } from '@app/hooks/api/workspace/queries'
|
||||
import { ProjectLayout } from '@app/layouts/ProjectLayout'
|
||||
import { workspaceKeys } from "@app/hooks/api";
|
||||
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticate/_inject-org-details/kms/$projectId/_kms-layout',
|
||||
"/_authenticate/_inject-org-details/kms/$projectId/_kms-layout"
|
||||
)({
|
||||
component: ProjectLayout,
|
||||
beforeLoad: async ({ params, context }) => {
|
||||
await context.queryClient.ensureQueryData({
|
||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||
queryFn: () => fetchWorkspaceById(params.projectId),
|
||||
})
|
||||
queryFn: () => fetchWorkspaceById(params.projectId)
|
||||
});
|
||||
|
||||
await context.queryClient.ensureQueryData({
|
||||
queryKey: roleQueryKeys.getUserProjectPermissions({
|
||||
workspaceId: params.projectId,
|
||||
workspaceId: params.projectId
|
||||
}),
|
||||
queryFn: () =>
|
||||
fetchUserProjectPermissions({ workspaceId: params.projectId }),
|
||||
})
|
||||
},
|
||||
})
|
||||
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,36 +1,27 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import {
|
||||
fetchOrganizationById,
|
||||
organizationKeys,
|
||||
} from '@app/hooks/api/organization/queries'
|
||||
import {
|
||||
fetchUserOrgPermissions,
|
||||
roleQueryKeys,
|
||||
} from '@app/hooks/api/roles/queries'
|
||||
import {
|
||||
fetchOrgSubscription,
|
||||
subscriptionQueryKeys,
|
||||
} from '@app/hooks/api/subscriptions/queries'
|
||||
import { fetchOrganizationById, organizationKeys } 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
|
||||
export const Route = createFileRoute('/_authenticate/_inject-org-details')({
|
||||
export const Route = createFileRoute("/_authenticate/_inject-org-details")({
|
||||
beforeLoad: async ({ context }) => {
|
||||
const organizationId = context.organizationId!
|
||||
const organizationId = context.organizationId!;
|
||||
await context.queryClient.ensureQueryData({
|
||||
queryKey: organizationKeys.getOrgById(organizationId),
|
||||
queryFn: () => fetchOrganizationById(organizationId),
|
||||
})
|
||||
queryFn: () => fetchOrganizationById(organizationId)
|
||||
});
|
||||
|
||||
await context.queryClient.ensureQueryData({
|
||||
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId),
|
||||
queryFn: () => fetchOrgSubscription(organizationId),
|
||||
})
|
||||
queryFn: () => fetchOrgSubscription(organizationId)
|
||||
});
|
||||
|
||||
await context.queryClient.ensureQueryData({
|
||||
queryKey: roleQueryKeys.getUserOrgPermissions({ orgId: organizationId }),
|
||||
queryFn: () => fetchUserOrgPermissions({ orgId: organizationId }),
|
||||
})
|
||||
return { organizationId }
|
||||
},
|
||||
})
|
||||
queryFn: () => fetchUserOrgPermissions({ orgId: 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"
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: "/organization/memberships/$membershipId" as const,
|
||||
to: "/organization/members/$membershipId" as const,
|
||||
params: {
|
||||
membershipId: orgMembershipId
|
||||
}
|
||||
@@ -401,7 +401,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate({
|
||||
to: "/organization/memberships/$membershipId" as const,
|
||||
to: "/organization/members/$membershipId" as const,
|
||||
params: {
|
||||
membershipId: orgMembershipId
|
||||
}
|
||||
|
||||
@@ -34,8 +34,7 @@ import { isCustomOrgRole } from "@app/helpers/roles";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteOrgRole, useGetOrgRoles, useUpdateOrg } from "@app/hooks/api";
|
||||
import { TOrgRole } from "@app/hooks/api/roles/types";
|
||||
|
||||
import { RoleModal } from "../../../roles/$roleId/-components";
|
||||
import { RoleModal } from "@app/pages/organization/RoleByIDPage/components/RoleModal";
|
||||
|
||||
export const OrgRoleTable = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { zodValidator } from '@tanstack/zod-adapter'
|
||||
import { z } from 'zod'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
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({
|
||||
selectedTab: z.string().catch(OrgAccessControlTabSections.Member),
|
||||
action: z.string().catch(''),
|
||||
})
|
||||
action: z.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticate/_inject-org-details/organization/_layout/access-management',
|
||||
"/_authenticate/_inject-org-details/organization/_layout/access-management"
|
||||
)({
|
||||
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(
|
||||
'/_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(
|
||||
'/_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(
|
||||
'/_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(
|
||||
'/_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"
|
||||
});
|
||||
navigate({
|
||||
to: "/organization/members" as const,
|
||||
to: "/organization/access-management" as const,
|
||||
search: {
|
||||
selectedTab: TabSections.Groups
|
||||
}
|
||||
@@ -90,7 +90,7 @@ const Page = () => {
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={() => {
|
||||
navigate({
|
||||
to: "/organization/members" as const,
|
||||
to: "/organization/access-management" as const,
|
||||
search: {
|
||||
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(
|
||||
'/_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 { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -32,6 +31,9 @@ import { Identity } from "@app/hooks/api/identities/types";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
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 {
|
||||
IdentityAuthenticationSection,
|
||||
IdentityClientSecretModal,
|
||||
@@ -86,7 +88,7 @@ const Page = () => {
|
||||
|
||||
handlePopUpClose("deleteIdentity");
|
||||
navigate({
|
||||
to: "/organization/members",
|
||||
to: "/organization/access-management",
|
||||
search: {
|
||||
selectedTab: OrgAccessControlTabSections.Identities
|
||||
}
|
||||
@@ -170,7 +172,7 @@ const Page = () => {
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={() => {
|
||||
navigate({
|
||||
to: "/organization/members",
|
||||
to: "/organization/access-management",
|
||||
search: {
|
||||
selectedTab: OrgAccessControlTabSections.Identities
|
||||
}
|
||||
|
||||
@@ -59,7 +59,10 @@ export const IdentityProjectRow = ({
|
||||
onClick={() => {
|
||||
if (isAccessible) {
|
||||
navigate({
|
||||
to: `/${project?.type}/${project.id}/access` as const,
|
||||
to: `/${project?.type}/$projectId/access-management` as const,
|
||||
params: {
|
||||
projectId: project.id
|
||||
},
|
||||
search: {
|
||||
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(
|
||||
'/_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(
|
||||
'/_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 { NoOrgPage } from './NoOrgPage'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { NoOrgPage } from "./NoOrgPage";
|
||||
|
||||
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");
|
||||
navigate({
|
||||
to: "/organization/members" as const,
|
||||
to: "/organization/access-management" as const,
|
||||
search: {
|
||||
selectedTab: OrgAccessControlTabSections.Roles
|
||||
}
|
||||
@@ -85,7 +85,7 @@ export const Page = () => {
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={() => {
|
||||
navigate({
|
||||
to: "/organization/members" as const,
|
||||
to: "/organization/access-management" as const,
|
||||
search: {
|
||||
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(
|
||||
'/_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(
|
||||
'/_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 { Button, NoticeBanner } from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
@@ -22,7 +23,7 @@ import { SecretScanningLogsTable } from "./components";
|
||||
export const SecretScanningPage = withPermission(
|
||||
() => {
|
||||
const queryParams = useSearch({
|
||||
from: "/_authenticate/_ctx-org-details/organization/_layout-org/secret-scanning/"
|
||||
from: ROUTE_PATHS.Organization.SecretScanning.id
|
||||
});
|
||||
const { config } = useServerConfig();
|
||||
const { currentOrg } = useOrganization();
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { zodValidator } from '@tanstack/zod-adapter'
|
||||
import { z } from 'zod'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretScanningPage } from './SecretScanningPage'
|
||||
import { SecretScanningPage } from "./SecretScanningPage";
|
||||
|
||||
const SecretScanningQueryParams = z.object({
|
||||
state: z.string().catch(''),
|
||||
installation_id: z.string().catch(''),
|
||||
})
|
||||
state: z.string().catch(""),
|
||||
installation_id: z.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticate/_inject-org-details/organization/_layout/secret-scanning',
|
||||
"/_authenticate/_inject-org-details/organization/_layout/secret-scanning"
|
||||
)({
|
||||
component: SecretScanningPage,
|
||||
validateSearch: zodValidator(SecretScanningQueryParams),
|
||||
})
|
||||
validateSearch: zodValidator(SecretScanningQueryParams)
|
||||
});
|
||||
|
||||
@@ -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(
|
||||
'/_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"
|
||||
onClick={() =>
|
||||
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 { useDeleteOrgById } from "@app/hooks/api";
|
||||
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 = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useSearch } from "@tanstack/react-router";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
|
||||
|
||||
import { AuditLogStreamsTab } from "../AuditLogStreamTab";
|
||||
@@ -24,7 +25,7 @@ const tabs = [
|
||||
];
|
||||
export const OrgTabGroup = () => {
|
||||
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);
|
||||
|
||||
|
||||
@@ -6,22 +6,22 @@ import { twMerge } from "tailwind-merge";
|
||||
import { z } from "zod";
|
||||
|
||||
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 { ProjectPermissionSub } from "@app/context";
|
||||
import { isCustomProjectRole } from "@app/helpers/roles";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { TProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates";
|
||||
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 = {
|
||||
projectTemplate: TProjectTemplate;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { zodValidator } from '@tanstack/zod-adapter'
|
||||
import { z } from 'zod'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SettingsPage } from './SettingsPage'
|
||||
import { SettingsPage } from "./SettingsPage";
|
||||
|
||||
const SettingsPageQueryParams = z.object({
|
||||
selectedTab: z.string().catch(''),
|
||||
})
|
||||
selectedTab: z.string().catch("")
|
||||
});
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/_authenticate/_inject-org-details/organization/_layout/settings',
|
||||
"/_authenticate/_inject-org-details/organization/_layout/settings"
|
||||
)({
|
||||
component: SettingsPage,
|
||||
validateSearch: zodValidator(SettingsPageQueryParams),
|
||||
})
|
||||
validateSearch: zodValidator(SettingsPageQueryParams)
|
||||
});
|
||||
|
||||
@@ -99,7 +99,7 @@ const Page = withPermission(
|
||||
|
||||
handlePopUpClose("removeMember");
|
||||
navigate({
|
||||
to: "/organization/members" as const,
|
||||
to: "/organization/access-management" as const,
|
||||
search: {
|
||||
selectedTab: OrgAccessControlTabSections.Member
|
||||
}
|
||||
@@ -125,7 +125,7 @@ const Page = withPermission(
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={() => {
|
||||
navigate({
|
||||
to: "/organization/members" as const,
|
||||
to: "/organization/access-management" as const,
|
||||
search: {
|
||||
selectedTab: OrgAccessControlTabSections.Member
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ import { IconButton, Tooltip } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||
import { withPermission } from "@app/hoc";
|
||||
import { OrgUser } from "@app/hooks/api/types";
|
||||
|
||||
import { LogsSection } from "../../../../audit-logs/-components";
|
||||
import { LogsSection } from "@app/pages/organization/AuditLogsPage/components";
|
||||
|
||||
type Props = {
|
||||
orgMembership: OrgUser;
|
||||
|
||||
@@ -52,7 +52,7 @@ export const UserProjectRow = ({
|
||||
onClick={() => {
|
||||
if (isAccessible) {
|
||||
navigate({
|
||||
to: `/${project.type}/$projectId/access` as const,
|
||||
to: `/${project.type}/$projectId/access-management` as const,
|
||||
params: {
|
||||
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(
|
||||
'/_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(
|
||||
'/_authenticate/_inject-org-details/organization/_layout',
|
||||
)({
|
||||
component: OrganizationLayout,
|
||||
})
|
||||
export const Route = createFileRoute("/_authenticate/_inject-org-details/organization/_layout")({
|
||||
component: OrganizationLayout
|
||||
});
|
||||
|
||||
@@ -28,7 +28,7 @@ const Page = withProjectPermission(
|
||||
|
||||
const updateSelectedTab = (tab: string) => {
|
||||
navigate({
|
||||
to: `/${currentWorkspace.type}/$projectId/access` as const,
|
||||
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
||||
search: (prev) => ({ ...prev, selectedTab: tab }),
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
|
||||
@@ -15,4 +15,3 @@ export const GroupsTab = () => {
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
<div className="text-sm">
|
||||
All groups in your organization have already been added to this project.
|
||||
</div>
|
||||
<Link to={"/organization/members" as const}>
|
||||
<Link to={"/organization/access-management" as const}>
|
||||
<Button variant="outline_bg">Create a new group</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { GroupsSection } from "./GroupsSection";
|
||||
export { GroupsSection } from "./GroupsSection";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { GroupsSection } from "./GroupsSection";
|
||||
export { GroupsSection } from "./GroupsSection";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { GroupsTab } from "./GroupsTab";
|
||||
export { GroupsTab } from "./GroupsTab";
|
||||
|
||||
@@ -182,7 +182,7 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
<div className="text-sm">
|
||||
All identities in your organization have already been added to this project.
|
||||
</div>
|
||||
<Link to={"/organization/members" as const}>
|
||||
<Link to={"/organization/access-management" as const}>
|
||||
<Button isDisabled={isRolesLoading} isLoading={isRolesLoading} variant="outline_bg">
|
||||
Create a new identity
|
||||
</Button>
|
||||
|
||||
@@ -211,7 +211,7 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
) : (
|
||||
<div className="flex flex-col space-y-4">
|
||||
<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>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@ import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@a
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteProjectRole, useGetProjectRoles } from "@app/hooks/api";
|
||||
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 = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -18,7 +18,7 @@ type DeleteModalData = { name: string; id: string };
|
||||
export const ServiceTokenSection = withProjectPermission(
|
||||
() => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
const deleteServiceToken = useDeleteServiceToken();
|
||||
|
||||
const { popUp, handlePopUpToggle, handlePopUpClose, handlePopUpOpen } = usePopUp([
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { zodValidator } from '@tanstack/zod-adapter'
|
||||
import { z } from 'zod'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
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({
|
||||
selectedTab: z
|
||||
.nativeEnum(ProjectAccessControlTabs)
|
||||
.catch(ProjectAccessControlTabs.Member),
|
||||
})
|
||||
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member)
|
||||
});
|
||||
|
||||
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,
|
||||
validateSearch: zodValidator(AccessControlPageQuerySchema),
|
||||
})
|
||||
validateSearch: zodValidator(AccessControlPageQuerySchema)
|
||||
});
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { zodValidator } from '@tanstack/zod-adapter'
|
||||
import { z } from 'zod'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
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({
|
||||
selectedTab: z
|
||||
.nativeEnum(ProjectAccessControlTabs)
|
||||
.catch(ProjectAccessControlTabs.Member),
|
||||
})
|
||||
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member)
|
||||
});
|
||||
|
||||
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,
|
||||
validateSearch: zodValidator(AccessControlPageQuerySchema),
|
||||
})
|
||||
validateSearch: zodValidator(AccessControlPageQuerySchema)
|
||||
});
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { zodValidator } from '@tanstack/zod-adapter'
|
||||
import { z } from 'zod'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { zodValidator } from "@tanstack/zod-adapter";
|
||||
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({
|
||||
selectedTab: z
|
||||
.nativeEnum(ProjectAccessControlTabs)
|
||||
.catch(ProjectAccessControlTabs.Member),
|
||||
})
|
||||
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member)
|
||||
});
|
||||
|
||||
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,
|
||||
validateSearch: zodValidator(AccessControlPageQuerySchema),
|
||||
})
|
||||
validateSearch: zodValidator(AccessControlPageQuerySchema)
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ const Page = () => {
|
||||
});
|
||||
handlePopUpClose("deleteIdentity");
|
||||
navigate({
|
||||
to: `/${currentWorkspace.type}/$projectId/access` as const,
|
||||
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
||||
params: {
|
||||
projectId: workspaceId
|
||||
},
|
||||
@@ -90,7 +90,7 @@ const Page = () => {
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={() => {
|
||||
navigate({
|
||||
to: `/${currentWorkspace.type}/$projectId/access` as const,
|
||||
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
||||
params: {
|
||||
projectId: workspaceId
|
||||
},
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { IdentityDetailsByIDPage } from './IdentityDetailsByIDPage'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
|
||||
|
||||
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 { IdentityDetailsByIDPage } from './IdentityDetailsByIDPage'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
|
||||
|
||||
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 { IdentityDetailsByIDPage } from './IdentityDetailsByIDPage'
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
|
||||
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
|
||||
|
||||
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"
|
||||
});
|
||||
navigate({
|
||||
to: `/${currentWorkspace.type}/$projectId/access` as const,
|
||||
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
}
|
||||
@@ -90,7 +90,7 @@ export const Page = () => {
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
onClick={() => {
|
||||
navigate({
|
||||
to: `/${currentWorkspace.type}/$projectId/access` as const,
|
||||
to: `/${currentWorkspace.type}/$projectId/access-management` as const,
|
||||
params: {
|
||||
projectId: currentWorkspace.id
|
||||
}
|
||||
|
||||
@@ -15,16 +15,6 @@ import { z } from "zod";
|
||||
|
||||
import { TtlFormLabel } from "@app/components/features";
|
||||
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 {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
@@ -52,6 +42,16 @@ import {
|
||||
useUpdateProjectUserAdditionalPrivilege
|
||||
} from "@app/hooks/api";
|
||||
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 = {
|
||||
privilegeId?: string;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user