mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 05:28:51 +00:00
feat: completed base setup
This commit is contained in:
@@ -24,6 +24,8 @@ apiRequest.interceptors.request.use((config) => {
|
||||
const token = getAuthToken();
|
||||
const providerAuthToken = SecurityClient.getProviderAuthToken();
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
if (config.headers) {
|
||||
if (signupTempToken) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
@@ -38,6 +40,10 @@ apiRequest.interceptors.request.use((config) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
config.headers.Authorization = `Bearer ${providerAuthToken}`;
|
||||
}
|
||||
const subOrganization = params.get("subOrganization");
|
||||
if (subOrganization) {
|
||||
config.headers.set("x-infisical-org", subOrganization);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
|
||||
@@ -15,5 +15,12 @@ export const useOrganization = () => {
|
||||
staleTime: Infinity
|
||||
});
|
||||
|
||||
return { currentOrg };
|
||||
return {
|
||||
currentOrg: {
|
||||
...currentOrg,
|
||||
id: currentOrg?.subOrganization?.id || currentOrg?.id,
|
||||
parentOrgId: currentOrg.id
|
||||
},
|
||||
isSubOrganization: Boolean(currentOrg.subOrganization)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -48,6 +48,7 @@ export * from "./sshCertificateTemplates";
|
||||
export * from "./sshHost";
|
||||
export * from "./sshHostGroup";
|
||||
export * from "./ssoConfig";
|
||||
export * from "./subOrganizations";
|
||||
export * from "./subscriptions";
|
||||
export * from "./tags";
|
||||
export * from "./trustedIps";
|
||||
|
||||
@@ -64,7 +64,9 @@ export const useGetOrganizations = () => {
|
||||
export const fetchOrganizationById = async (id: string) => {
|
||||
const {
|
||||
data: { organization }
|
||||
} = await apiRequest.get<{ organization: Organization }>(`/api/v1/organization/${id}`);
|
||||
} = await apiRequest.get<{
|
||||
organization: Organization & { subOrganization?: { id: string; name: string } };
|
||||
}>(`/api/v1/organization/${id}`);
|
||||
return organization;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export { useCreateSubOrganization } from "./mutations";
|
||||
export { subOrganizationsQuery } from "./queries";
|
||||
export type {
|
||||
TCreateSubOrganizationDTO,
|
||||
TListSubOrganizationsDTO,
|
||||
TSubOrganization
|
||||
} from "./types";
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { subOrganizationsQuery } from "./queries";
|
||||
import { TCreateSubOrganizationDTO, TSubOrganization } from "./types";
|
||||
|
||||
export const useCreateSubOrganization = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (dto: TCreateSubOrganizationDTO) => {
|
||||
const { data } = await apiRequest.post<{ organization: TSubOrganization }>(
|
||||
"/api/v1/sub-organizations",
|
||||
dto
|
||||
);
|
||||
return data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: subOrganizationsQuery.allKey() });
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TListSubOrganizationsDTO, TSubOrganization } from "./types";
|
||||
|
||||
export const subOrganizationsQuery = {
|
||||
allKey: () => ["sub-organizations"] as const,
|
||||
listKey: (params?: TListSubOrganizationsDTO) =>
|
||||
[...subOrganizationsQuery.allKey(), "list", params] as const,
|
||||
list: (params: TListSubOrganizationsDTO) =>
|
||||
queryOptions({
|
||||
queryKey: subOrganizationsQuery.listKey(params),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ organizations: TSubOrganization[] }>(
|
||||
"/api/v1/sub-organizations",
|
||||
{
|
||||
params: {
|
||||
limit: params.limit,
|
||||
offset: params.offset,
|
||||
isAccessible: params.isAccessible
|
||||
}
|
||||
}
|
||||
);
|
||||
return data.organizations;
|
||||
}
|
||||
})
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
export type TSubOrganization = {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type TCreateSubOrganizationDTO = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TListSubOrganizationsDTO = {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
isAccessible?: boolean;
|
||||
};
|
||||
@@ -13,6 +13,7 @@ export type SubscriptionPlan = {
|
||||
customRateLimits: boolean;
|
||||
pitRecovery: boolean;
|
||||
githubOrgSync: boolean;
|
||||
subOrganization?: boolean;
|
||||
ipAllowlisting: boolean;
|
||||
rbac: boolean;
|
||||
secretVersioning: boolean;
|
||||
|
||||
@@ -6,12 +6,14 @@ import {
|
||||
faBook,
|
||||
faCaretDown,
|
||||
faCheck,
|
||||
faCubes,
|
||||
faEnvelope,
|
||||
faExclamationTriangle,
|
||||
faGlobe,
|
||||
faInfinity,
|
||||
faInfo,
|
||||
faInfoCircle,
|
||||
faPlus,
|
||||
faServer,
|
||||
faSignOut,
|
||||
faToolbox,
|
||||
@@ -19,7 +21,7 @@ import {
|
||||
faUsers
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Link, useLocation, useNavigate, useRouter, useRouterState } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
@@ -34,6 +36,9 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
DropdownSubMenu,
|
||||
DropdownSubMenuContent,
|
||||
DropdownSubMenuTrigger,
|
||||
IconButton,
|
||||
Modal,
|
||||
ModalContent,
|
||||
@@ -44,7 +49,7 @@ import { envConfig } from "@app/config/env";
|
||||
import { useOrganization, useSubscription, useUser } from "@app/context";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { projectKeys, useGetOrganizations, useGetOrgTrialUrl, useLogoutUser } from "@app/hooks/api";
|
||||
import { projectKeys, subOrganizationsQuery, useGetOrganizations, useGetOrgTrialUrl, useLogoutUser } from "@app/hooks/api";
|
||||
import { authKeys, selectOrganization } from "@app/hooks/api/auth/queries";
|
||||
import { MfaMethod } from "@app/hooks/api/auth/types";
|
||||
import { getAuthToken } from "@app/hooks/api/reactQuery";
|
||||
@@ -54,6 +59,7 @@ import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
|
||||
|
||||
import { ServerAdminsPanel } from "../ServerAdminsPanel/ServerAdminsPanel";
|
||||
import { NotificationDropdown } from "./NotificationDropdown";
|
||||
import { NewSubOrganizationForm } from "./NewSubOrganizationForm";
|
||||
|
||||
const getPlan = (subscription: SubscriptionPlan) => {
|
||||
if (subscription.groups) return "Enterprise";
|
||||
@@ -119,9 +125,15 @@ export const INFISICAL_SUPPORT_OPTIONS = [
|
||||
export const Navbar = () => {
|
||||
const { user } = useUser();
|
||||
const { subscription } = useSubscription();
|
||||
const { currentOrg } = useOrganization();
|
||||
const { currentOrg, isSubOrganization } = useOrganization();
|
||||
|
||||
const [showAdminsModal, setShowAdminsModal] = useState(false);
|
||||
const [showSubOrgForm, setShowSubOrgForm] = useState(false);
|
||||
const [showCardDeclinedModal, setShowCardDeclinedModal] = useState(false);
|
||||
const { data: subOrganizations = [] } = useQuery({
|
||||
...subOrganizationsQuery.list({ limit: 500 }),
|
||||
enabled: Boolean(subscription.subOrganization) && !isSubOrganization
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (subscription?.cardDeclined && !sessionStorage.getItem("paymentFailed")) {
|
||||
@@ -517,6 +529,7 @@ export const Navbar = () => {
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<Modal isOpen={showCardDeclinedModal} onOpenChange={setShowCardDeclinedModal}>
|
||||
<ModalContent
|
||||
title={
|
||||
@@ -560,6 +573,16 @@ export const Navbar = () => {
|
||||
</div>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<Modal isOpen={showSubOrgForm} onOpenChange={setShowSubOrgForm}>
|
||||
<ModalContent
|
||||
title="Create Sub-Organizations"
|
||||
subTitle="Define a new sub-organization under your current organization."
|
||||
>
|
||||
<div className="mb-2">
|
||||
<NewSubOrganizationForm onClose={() => setShowSubOrgForm(true)} />
|
||||
</div>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<Modal isOpen={showAdminsModal} onOpenChange={setShowAdminsModal}>
|
||||
<ModalContent title="Server Administrators" subTitle="View all server administrators">
|
||||
<div className="mb-2">
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
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 { GenericResourceNameSchema } from "@app/lib/schemas";
|
||||
import { useCreateSubOrganization } from "@app/hooks/api";
|
||||
|
||||
type ContentProps = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const AddOrgSchema = z.object({
|
||||
name: GenericResourceNameSchema.nonempty("Suborganization name required")
|
||||
});
|
||||
|
||||
type FormData = z.infer<typeof AddOrgSchema>;
|
||||
|
||||
export const NewSubOrganizationForm = ({ onClose }: ContentProps) => {
|
||||
const createSubOrg = useCreateSubOrganization();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { isSubmitting }
|
||||
} = useForm({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
invitees: []
|
||||
},
|
||||
resolver: zodResolver(AddOrgSchema)
|
||||
});
|
||||
|
||||
const onSubmit = async ({ name }: FormData) => {
|
||||
try {
|
||||
await createSubOrg.mutateAsync({
|
||||
name
|
||||
});
|
||||
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Successfully created sub organization"
|
||||
});
|
||||
onClose();
|
||||
} catch {
|
||||
createNotification({
|
||||
text: "Failed to create sub organization",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Controller
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message} label="Name">
|
||||
<Input autoFocus value={value} onChange={onChange} placeholder="My Organization" />
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="name"
|
||||
/>
|
||||
<div className="flex w-full gap-4 pt-4">
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
colorSchema="secondary"
|
||||
>
|
||||
Add Sub-Organization
|
||||
</Button>
|
||||
<Button onClick={() => onClose()} variant="plain" colorSchema="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,14 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { createFileRoute, retainSearchParams } from "@tanstack/react-router";
|
||||
|
||||
import { OrganizationLayout } from "@app/layouts/OrganizationLayout";
|
||||
import { z } from "zod";
|
||||
|
||||
export const Route = createFileRoute("/_authenticate/_inject-org-details/_org-layout")({
|
||||
component: OrganizationLayout
|
||||
component: OrganizationLayout,
|
||||
validateSearch: z.object({
|
||||
subOrganization: z.string().optional()
|
||||
}),
|
||||
search: {
|
||||
middlewares: [retainSearchParams(["subOrganization"])]
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user