Merge pull request #18170 from BerriAI/litellm_ui_cloudzero_delete

[Feature] UI - Cloudzero Delete Settings
This commit is contained in:
yuneng-jiang 2025-12-19 11:38:28 -08:00 committed by GitHub
commit cacaf54b95
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 114 additions and 21 deletions

View file

@ -1,7 +1,7 @@
import { getProxyBaseUrl } from "@/components/networking";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { createQueryKeys } from "../common/queryKeysFactory";
import { CloudZeroSettings } from "@/components/CloudZeroCostTracking/types";
import { getProxyBaseUrl } from "@/components/networking";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { createQueryKeys } from "../common/queryKeysFactory";
const cloudZeroSettingsKeys = createQueryKeys("cloudZeroSettings");
@ -54,6 +54,11 @@ interface UpdateResponse {
status: string;
}
interface DeleteResponse {
message: string;
status: string;
}
const updateCloudZeroSettings = async (accessToken: string, params: UpdateParams): Promise<UpdateResponse> => {
const proxyBaseUrl = getProxyBaseUrl();
const url = proxyBaseUrl ? `${proxyBaseUrl}/cloudzero/settings` : `/cloudzero/settings`;
@ -98,3 +103,43 @@ export const useCloudZeroUpdateSettings = (accessToken: string) => {
},
});
};
const deleteCloudZeroSettings = async (accessToken: string): Promise<DeleteResponse> => {
const proxyBaseUrl = getProxyBaseUrl();
const url = proxyBaseUrl ? `${proxyBaseUrl}/cloudzero/delete` : `/cloudzero/delete`;
const response = await fetch(url, {
method: "DELETE",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const errorMessage =
errorData?.error?.message || errorData?.message || errorData?.detail || "Failed to delete CloudZero settings";
throw new Error(errorMessage);
}
const data = await response.json();
return data;
};
export const useCloudZeroDeleteSettings = (accessToken: string) => {
const queryClient = useQueryClient();
return useMutation<DeleteResponse, Error, void>({
mutationFn: async () => {
if (!accessToken) {
throw new Error("Access token is required");
}
return await deleteCloudZeroSettings(accessToken);
},
onSuccess: () => {
// Invalidate the settings query to refetch updated data
queryClient.invalidateQueries({ queryKey: cloudZeroSettingsKeys.list({}) });
},
});
};

View file

@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { CloudZeroIntegrationSettings } from "./CloudZeroIntegrationSettings";
import { CloudZeroSettings } from "./types";
@ -68,4 +68,15 @@ describe("CloudZeroIntegrationSettings", () => {
expect(screen.getByText("Connection ID")).toBeInTheDocument();
expect(screen.getByText("Timezone")).toBeInTheDocument();
});
it("should display the correct values from settings", () => {
render(
<QueryClientProvider client={queryClient}>
<CloudZeroIntegrationSettings settings={mockSettings} onSettingsUpdated={vi.fn()} />
</QueryClientProvider>,
);
expect(screen.getByText(mockSettings.api_key_masked)).toBeInTheDocument();
expect(screen.getByText(mockSettings.connection_id)).toBeInTheDocument();
});
});

View file

@ -1,6 +1,8 @@
import { useCloudZeroDryRun } from "@/app/(dashboard)/hooks/cloudzero/useCloudZeroDryRun";
import { useCloudZeroExport } from "@/app/(dashboard)/hooks/cloudzero/useCloudZeroExport";
import { useCloudZeroDeleteSettings } from "@/app/(dashboard)/hooks/cloudzero/useCloudZeroSettings";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
import { Alert, Button, Card, Descriptions, Divider, message, Popconfirm, Tag } from "antd";
import { CheckCircle, Edit, Play, Trash2, Upload } from "lucide-react";
import { useState } from "react";
@ -15,9 +17,11 @@ interface CloudZeroIntegrationSettingsProps {
export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: CloudZeroIntegrationSettingsProps) {
const { accessToken } = useAuthorized();
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const dryRunMutation = useCloudZeroDryRun(accessToken || "");
const exportMutation = useCloudZeroExport(accessToken || "");
const deleteMutation = useCloudZeroDeleteSettings(accessToken || "");
const handleDryRun = () => {
if (!accessToken) return;
@ -66,10 +70,27 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl
setIsEditModalOpen(false);
};
const handleDelete = async () => {
// Note: Delete functionality is not yet implemented in the backend API
// This would require a DELETE endpoint at /cloudzero/settings
message.warning("Delete functionality is not yet available. Please contact support.");
const handleDeleteClick = () => {
setIsDeleteModalOpen(true);
};
const handleDeleteConfirm = () => {
if (!accessToken) return;
deleteMutation.mutate(undefined, {
onSuccess: () => {
message.success("CloudZero integration deleted successfully");
setIsDeleteModalOpen(false);
onSettingsUpdated();
},
onError: (error) => {
message.error(error?.message || "Failed to delete CloudZero integration");
},
});
};
const handleDeleteCancel = () => {
setIsDeleteModalOpen(false);
};
return (
@ -89,19 +110,14 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl
<Button icon={<Edit size={16} />} onClick={handleEdit} className="flex items-center gap-2">
Edit
</Button>
<Popconfirm
title="Delete Integration"
description="Delete functionality is not yet available in the API. This button is disabled."
okText="OK"
cancelText="Cancel"
okButtonProps={{
danger: true,
}}
<Button
danger
icon={<Trash2 size={16} />}
onClick={handleDeleteClick}
className="flex items-center gap-2"
>
<Button danger icon={<Trash2 size={16} />} disabled className="flex items-center gap-2">
Delete
</Button>
</Popconfirm>
Delete
</Button>
</div>
}
className="shadow-sm"
@ -187,6 +203,27 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl
onCancel={handleEditModalCancel}
settings={settings}
/>
<DeleteResourceModal
isOpen={isDeleteModalOpen}
title="Delete CloudZero Integration?"
message="Are you sure you want to delete this CloudZero integration? All associated settings and configurations will be permanently removed."
resourceInformationTitle="Integration Details"
resourceInformation={[
{
label: "Connection ID",
value: settings.connection_id,
code: true,
},
{
label: "Timezone",
value: settings.timezone || "Default (UTC)",
},
]}
onCancel={handleDeleteCancel}
onOk={handleDeleteConfirm}
confirmLoading={deleteMutation.isPending}
/>
</>
);
}