mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
[Feature] UI - Projects: Add delete project action to projects table
Add the ability to delete projects from the projects list page: - New useDeleteProject hook calling DELETE /project/delete - Actions column with TableIconActionButton (Delete variant) - DeleteResourceModal with confirmation requiring project name input - Alert warns that keys must be unlinked before deletion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
13ea00acdd
commit
926d95cd19
2 changed files with 101 additions and 0 deletions
|
|
@ -0,0 +1,54 @@
|
|||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
getProxyBaseUrl,
|
||||
getGlobalLitellmHeaderName,
|
||||
deriveErrorMessage,
|
||||
handleError,
|
||||
} from "@/components/networking";
|
||||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import { projectKeys } from "./useProjects";
|
||||
|
||||
// ── Fetch function ───────────────────────────────────────────────────────────
|
||||
|
||||
const deleteProjects = async (
|
||||
accessToken: string,
|
||||
projectIds: string[],
|
||||
): Promise<void> => {
|
||||
const baseUrl = getProxyBaseUrl();
|
||||
const url = `${baseUrl}/project/delete`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
[getGlobalLitellmHeaderName()]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ project_ids: projectIds }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
handleError(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
// ── Hook ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const useDeleteProject = () => {
|
||||
const { accessToken } = useAuthorized();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<void, Error, string[]>({
|
||||
mutationFn: async (projectIds) => {
|
||||
if (!accessToken) {
|
||||
throw new Error("Access token is required");
|
||||
}
|
||||
return deleteProjects(accessToken, projectIds);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: projectKeys.all });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { useDeleteProject } from "@/app/(dashboard)/hooks/projects/useDeleteProject";
|
||||
import { useProjects, ProjectResponse } from "@/app/(dashboard)/hooks/projects/useProjects";
|
||||
import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
|
||||
import { LoadingOutlined, PlusOutlined } from "@ant-design/icons";
|
||||
|
|
@ -8,6 +9,7 @@ import {
|
|||
Flex,
|
||||
Input,
|
||||
Layout,
|
||||
message,
|
||||
Pagination,
|
||||
Space,
|
||||
Spin,
|
||||
|
|
@ -20,6 +22,8 @@ import {
|
|||
import type { ColumnsType } from "antd/es/table";
|
||||
import { LayersIcon, SearchIcon } from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import DeleteResourceModal from "../common_components/DeleteResourceModal";
|
||||
import TableIconActionButton from "../common_components/IconActionButton/TableIconActionButtons/TableIconActionButton";
|
||||
import { CreateProjectModal } from "./ProjectModals/CreateProjectModal";
|
||||
import { ProjectDetail } from "./ProjectDetailsPage";
|
||||
|
||||
|
|
@ -31,8 +35,11 @@ export function ProjectsPage() {
|
|||
const { data: projects, isLoading } = useProjects();
|
||||
const { data: teams, isLoading: isTeamsLoading } = useTeams();
|
||||
|
||||
const deleteMutation = useDeleteProject();
|
||||
|
||||
const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null);
|
||||
const [isCreateModalVisible, setIsCreateModalVisible] = useState(false);
|
||||
const [projectToDelete, setProjectToDelete] = useState<ProjectResponse | null>(null);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const pageSize = 10;
|
||||
|
|
@ -151,6 +158,18 @@ export function ProjectsPage() {
|
|||
responsive: ["xl"],
|
||||
render: (date: string) => new Date(date).toLocaleDateString(),
|
||||
},
|
||||
{
|
||||
title: "Actions",
|
||||
key: "actions",
|
||||
width: 80,
|
||||
render: (_: unknown, record: ProjectResponse) => (
|
||||
<TableIconActionButton
|
||||
variant="Delete"
|
||||
tooltipText="Delete project"
|
||||
onClick={() => setProjectToDelete(record)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
if (selectedProjectId) {
|
||||
|
|
@ -231,6 +250,34 @@ export function ProjectsPage() {
|
|||
isOpen={isCreateModalVisible}
|
||||
onClose={() => setIsCreateModalVisible(false)}
|
||||
/>
|
||||
|
||||
<DeleteResourceModal
|
||||
isOpen={projectToDelete !== null}
|
||||
title="Delete Project"
|
||||
alertMessage="This action is irreversible. All keys must be unlinked from this project before it can be deleted."
|
||||
message="Are you sure you want to delete this project?"
|
||||
resourceInformationTitle="Project Information"
|
||||
resourceInformation={[
|
||||
{ label: "Name", value: projectToDelete?.project_alias || "—" },
|
||||
{ label: "Project ID", value: projectToDelete?.project_id, code: true },
|
||||
{ label: "Team", value: teamAliasMap.get(projectToDelete?.team_id ?? "") || projectToDelete?.team_id || "—" },
|
||||
]}
|
||||
onCancel={() => setProjectToDelete(null)}
|
||||
onOk={() => {
|
||||
if (!projectToDelete) return;
|
||||
deleteMutation.mutate([projectToDelete.project_id], {
|
||||
onSuccess: () => {
|
||||
message.success("Project deleted successfully");
|
||||
setProjectToDelete(null);
|
||||
},
|
||||
onError: (error) => {
|
||||
message.error(error.message || "Failed to delete project");
|
||||
},
|
||||
});
|
||||
}}
|
||||
confirmLoading={deleteMutation.isPending}
|
||||
requiredConfirmation={projectToDelete?.project_alias ?? undefined}
|
||||
/>
|
||||
</Content>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue