mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 11:38:55 +00:00
Merge remote-tracking branch 'origin' into token-auth
This commit is contained in:
@@ -81,7 +81,7 @@ const AlertDescription = forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={twMerge("text-sm [&_p]:leading-relaxed", className)} {...props} />
|
||||
<div ref={ref} className={twMerge("text-sm", className)} {...props} />
|
||||
));
|
||||
AlertDescription.displayName = "AlertDescription";
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { useCreateAdminUser, useUpdateServerConfig } from "./mutation";
|
||||
export { useGetServerConfig } from "./queries";
|
||||
export { useAdminDeleteUser, useCreateAdminUser, useUpdateServerConfig } from "./mutation";
|
||||
export { useAdminGetUsers, useGetServerConfig } from "./queries";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { organizationKeys } from "../organization/queries";
|
||||
import { User } from "../users/types";
|
||||
import { adminQueryKeys } from "./queries";
|
||||
import { adminQueryKeys, adminStandaloneKeys } from "./queries";
|
||||
import { TCreateAdminUserDTO, TServerConfig } from "./types";
|
||||
|
||||
export const useCreateAdminUser = () => {
|
||||
@@ -43,3 +43,19 @@ export const useUpdateServerConfig = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useAdminDeleteUser = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (userId: string) => {
|
||||
await apiRequest.delete(`/api/v1/admin/user-management/users/${userId}`);
|
||||
|
||||
return {};
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [adminStandaloneKeys.getUsers]
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||
import { useInfiniteQuery, useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TServerConfig } from "./types";
|
||||
import { User } from "../types";
|
||||
import { AdminGetUsersFilters, TServerConfig } from "./types";
|
||||
|
||||
export const adminStandaloneKeys = {
|
||||
getUsers: "get-users"
|
||||
};
|
||||
|
||||
export const adminQueryKeys = {
|
||||
serverConfig: () => ["server-config"] as const
|
||||
serverConfig: () => ["server-config"] as const,
|
||||
getUsers: (filters: AdminGetUsersFilters) => [adminStandaloneKeys.getUsers, { filters }] as const
|
||||
};
|
||||
|
||||
const fetchServerConfig = async () => {
|
||||
@@ -32,3 +38,24 @@ export const useGetServerConfig = ({
|
||||
...options,
|
||||
enabled: options?.enabled ?? true
|
||||
});
|
||||
|
||||
export const useAdminGetUsers = (filters: AdminGetUsersFilters) => {
|
||||
return useInfiniteQuery({
|
||||
queryKey: adminQueryKeys.getUsers(filters),
|
||||
queryFn: async ({ pageParam }) => {
|
||||
const { data } = await apiRequest.get<{ users: User[] }>(
|
||||
"/api/v1/admin/user-management/users",
|
||||
{
|
||||
params: {
|
||||
...filters,
|
||||
offset: pageParam
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return data.users;
|
||||
},
|
||||
getNextPageParam: (lastPage, pages) =>
|
||||
lastPage.length !== 0 ? pages.length * filters.limit : undefined
|
||||
});
|
||||
};
|
||||
|
||||
@@ -37,3 +37,8 @@ export type TCreateAdminUserDTO = {
|
||||
verifier: string;
|
||||
salt: string;
|
||||
};
|
||||
|
||||
export type AdminGetUsersFilters = {
|
||||
limit: number;
|
||||
searchTerm: string;
|
||||
};
|
||||
|
||||
@@ -9,8 +9,8 @@ export {
|
||||
useAddUserToOrg,
|
||||
useCreateAPIKey,
|
||||
useDeleteAPIKey,
|
||||
useDeleteMe,
|
||||
useDeleteOrgMembership,
|
||||
useDeleteUser,
|
||||
useGetMyAPIKeys,
|
||||
useGetMyAPIKeysV2,
|
||||
useGetMyIp,
|
||||
|
||||
@@ -28,6 +28,7 @@ export const userKeys = {
|
||||
myAPIKeys: ["api-keys"] as const,
|
||||
myAPIKeysV2: ["api-keys-v2"] as const,
|
||||
mySessions: ["sessions"] as const,
|
||||
listUsers: ["user-list"] as const,
|
||||
|
||||
myOrganizationProjects: (orgId: string) => [{ orgId }, "organization-projects"] as const
|
||||
};
|
||||
@@ -40,7 +41,7 @@ export const fetchUserDetails = async () => {
|
||||
|
||||
export const useGetUser = () => useQuery(userKeys.getUser, fetchUserDetails);
|
||||
|
||||
export const useDeleteUser = () => {
|
||||
export const useDeleteMe = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
|
||||
@@ -15,6 +15,8 @@ import queryString from "query-string";
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
@@ -40,7 +42,7 @@ export default function FlyioCreateIntegrationPage() {
|
||||
const { data: integrationAuth, isLoading: isIntegrationAuthLoading } = useGetIntegrationAuthById(
|
||||
(integrationAuthId as string) ?? ""
|
||||
);
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } =
|
||||
const { data: integrationAuthApps = [], isLoading: isIntegrationAuthAppsLoading } =
|
||||
useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
});
|
||||
@@ -130,6 +132,13 @@ export default function FlyioCreateIntegrationPage() {
|
||||
</Link>
|
||||
</div>
|
||||
</CardTitle>
|
||||
<div className="px-6 pb-4">
|
||||
<Alert hideTitle variant="warning">
|
||||
<AlertDescription>
|
||||
All current secrets linked to the related Fly.io project will be deleted before Infisical secrets are pushed to your Fly.io project.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
<FormControl label="Project Environment" className="px-6">
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
|
||||
+3
-3
@@ -2,17 +2,17 @@ import { useRouter } from "next/router";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
import { useDeleteUser } from "@app/hooks/api";
|
||||
import { useDeleteMe } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
export const DeleteAccountSection = () => {
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"deleteAccount"
|
||||
] as const);
|
||||
|
||||
const { mutateAsync: deleteUserMutateAsync, isLoading } = useDeleteUser();
|
||||
const { mutateAsync: deleteUserMutateAsync, isLoading } = useDeleteMe();
|
||||
|
||||
const handleDeleteAccountSubmit = async () => {
|
||||
try {
|
||||
|
||||
@@ -26,11 +26,13 @@ import { useGetOrganizations, useUpdateServerConfig } from "@app/hooks/api";
|
||||
|
||||
import { AuthPanel } from "./AuthPanel";
|
||||
import { RateLimitPanel } from "./RateLimitPanel";
|
||||
import { UserPanel } from "./UserPanel";
|
||||
|
||||
enum TabSections {
|
||||
Settings = "settings",
|
||||
Auth = "auth",
|
||||
RateLimit = "rate-limit"
|
||||
RateLimit = "rate-limit",
|
||||
Users = "users"
|
||||
}
|
||||
|
||||
enum SignUpModes {
|
||||
@@ -135,6 +137,7 @@ export const AdminDashboardPage = () => {
|
||||
<Tab value={TabSections.Settings}>General</Tab>
|
||||
<Tab value={TabSections.Auth}>Authentication</Tab>
|
||||
<Tab value={TabSections.RateLimit}>Rate Limit</Tab>
|
||||
<Tab value={TabSections.Users}>Users</Tab>
|
||||
</div>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.Settings}>
|
||||
@@ -320,6 +323,9 @@ export const AdminDashboardPage = () => {
|
||||
<TabPanel value={TabSections.RateLimit}>
|
||||
<RateLimitPanel />
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.Users}>
|
||||
<UserPanel />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import { useState } from "react";
|
||||
import { faMagnifyingGlass, faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
EmptyState,
|
||||
IconButton,
|
||||
Input,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { 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"]>,
|
||||
data: {
|
||||
username: string;
|
||||
id: string;
|
||||
}
|
||||
) => void;
|
||||
}) => {
|
||||
const [searchUserFilter, setSearchUserFilter] = useState("");
|
||||
const { user } = useUser();
|
||||
const userId = user?.id || "";
|
||||
const debounedSearchTerm = useDebounce(searchUserFilter, 500);
|
||||
|
||||
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useAdminGetUsers({
|
||||
limit: 20,
|
||||
searchTerm: debounedSearchTerm
|
||||
});
|
||||
|
||||
const isEmpty = !isLoading && !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>
|
||||
{isLoading && <TableSkeleton columns={4} innerKey="users" />}
|
||||
{!isLoading &&
|
||||
data?.pages?.map((users) =>
|
||||
users.map(({ username, email, firstName, lastName, id }) => {
|
||||
const name = firstName || lastName ? `${firstName} ${lastName}` : "-";
|
||||
|
||||
return (
|
||||
<Tr key={`user-${id}`} className="w-full">
|
||||
<Td className="w-5/12">{name}</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={() => handlePopUpOpen("removeUser", { username, id })}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</TBody>
|
||||
</Table>
|
||||
{!isLoading && 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"
|
||||
] 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 (err) {
|
||||
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}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user