forked from phoenix/litellm-mirror
(UI) Delete Internal Users on Admin UI (#6442)
* add /user/delete call * ui show modal asking if you want to delete user * fix delete user modal
This commit is contained in:
parent
b3141e1a5f
commit
fb9fb3467d
2 changed files with 120 additions and 0 deletions
|
@ -521,6 +521,39 @@ export const keyDeleteCall = async (accessToken: String, user_key: String) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const userDeleteCall = async (accessToken: string, userIds: string[]) => {
|
||||||
|
try {
|
||||||
|
const url = proxyBaseUrl ? `${proxyBaseUrl}/user/delete` : `/user/delete`;
|
||||||
|
console.log("in userDeleteCall:", userIds);
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
user_ids: userIds,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.text();
|
||||||
|
handleError(errorData);
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data);
|
||||||
|
//message.success("User(s) Deleted");
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to delete user(s):", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export const teamDeleteCall = async (accessToken: String, teamID: String) => {
|
export const teamDeleteCall = async (accessToken: String, teamID: String) => {
|
||||||
try {
|
try {
|
||||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/team/delete` : `/team/delete`;
|
const url = proxyBaseUrl ? `${proxyBaseUrl}/team/delete` : `/team/delete`;
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
} from "@tremor/react";
|
} from "@tremor/react";
|
||||||
|
|
||||||
import { message } from "antd";
|
import { message } from "antd";
|
||||||
|
import { Modal } from "antd";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
userInfoCall,
|
userInfoCall,
|
||||||
|
@ -43,6 +44,8 @@ import {
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from "@heroicons/react/outline";
|
} from "@heroicons/react/outline";
|
||||||
|
|
||||||
|
import { userDeleteCall } from "./networking";
|
||||||
|
|
||||||
interface ViewUserDashboardProps {
|
interface ViewUserDashboardProps {
|
||||||
accessToken: string | null;
|
accessToken: string | null;
|
||||||
token: string | null;
|
token: string | null;
|
||||||
|
@ -84,11 +87,42 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
||||||
const [selectedItem, setSelectedItem] = useState<null | any>(null);
|
const [selectedItem, setSelectedItem] = useState<null | any>(null);
|
||||||
const [editModalVisible, setEditModalVisible] = useState(false);
|
const [editModalVisible, setEditModalVisible] = useState(false);
|
||||||
const [selectedUser, setSelectedUser] = useState(null);
|
const [selectedUser, setSelectedUser] = useState(null);
|
||||||
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
|
const [userToDelete, setUserToDelete] = useState<string | null>(null);
|
||||||
const [possibleUIRoles, setPossibleUIRoles] = useState<
|
const [possibleUIRoles, setPossibleUIRoles] = useState<
|
||||||
Record<string, Record<string, string>>
|
Record<string, Record<string, string>>
|
||||||
>({});
|
>({});
|
||||||
const defaultPageSize = 25;
|
const defaultPageSize = 25;
|
||||||
|
|
||||||
|
const handleDelete = (userId: string) => {
|
||||||
|
setUserToDelete(userId);
|
||||||
|
setIsDeleteModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = async () => {
|
||||||
|
if (userToDelete && accessToken) {
|
||||||
|
try {
|
||||||
|
await userDeleteCall(accessToken, [userToDelete]);
|
||||||
|
message.success("User deleted successfully");
|
||||||
|
// Update the user list after deletion
|
||||||
|
if (userData) {
|
||||||
|
const updatedUserData = userData.filter(user => user.user_id !== userToDelete);
|
||||||
|
setUserData(updatedUserData);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting user:", error);
|
||||||
|
message.error("Failed to delete user");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
setUserToDelete(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelDelete = () => {
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
setUserToDelete(null);
|
||||||
|
};
|
||||||
|
|
||||||
const handleEditCancel = async () => {
|
const handleEditCancel = async () => {
|
||||||
setSelectedUser(null);
|
setSelectedUser(null);
|
||||||
setEditModalVisible(false);
|
setEditModalVisible(false);
|
||||||
|
@ -272,6 +306,12 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
||||||
>
|
>
|
||||||
View Keys
|
View Keys
|
||||||
</Icon>
|
</Icon>
|
||||||
|
<Icon
|
||||||
|
icon={TrashIcon}
|
||||||
|
onClick={() => handleDelete(user.user_id)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Icon>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
@ -293,6 +333,53 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
||||||
user={selectedUser}
|
user={selectedUser}
|
||||||
onSubmit={handleEditSubmit}
|
onSubmit={handleEditSubmit}
|
||||||
/>
|
/>
|
||||||
|
{isDeleteModalOpen && (
|
||||||
|
<div className="fixed z-10 inset-0 overflow-y-auto">
|
||||||
|
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 transition-opacity"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Modal Panel */}
|
||||||
|
<span
|
||||||
|
className="hidden sm:inline-block sm:align-middle sm:h-screen"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
​
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Confirmation Modal Content */}
|
||||||
|
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
|
||||||
|
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
|
<div className="sm:flex sm:items-start">
|
||||||
|
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
|
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
Delete User
|
||||||
|
</h3>
|
||||||
|
<div className="mt-2">
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
Are you sure you want to delete this user?
|
||||||
|
</p>
|
||||||
|
<p className="text-sm font-medium text-gray-900 mt-2">
|
||||||
|
User ID: {userToDelete}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||||
|
<Button onClick={confirmDelete} color="red" className="ml-2">
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
<Button onClick={cancelDelete}>Cancel</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
{renderPagination()}
|
{renderPagination()}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue