mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
feat: Hashicorp Vault config UI components
Add settings panel, edit modal, and delete flow for managing Hashicorp Vault configuration from the admin UI. Extract shared constants, use React Query hooks, and align error handling with deriveErrorMessage pattern.
This commit is contained in:
parent
0f4771fe19
commit
c98da10b84
8 changed files with 625 additions and 0 deletions
|
|
@ -0,0 +1,21 @@
|
|||
import { deleteHashicorpVaultConfig } from "@/components/networking";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { createQueryKeys } from "../common/queryKeysFactory";
|
||||
|
||||
const hashicorpVaultKeys = createQueryKeys("hashicorpVaultConfig");
|
||||
|
||||
export const useDeleteHashicorpVaultConfig = (accessToken: string | null) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!accessToken) {
|
||||
throw new Error("Access token is required");
|
||||
}
|
||||
return deleteHashicorpVaultConfig(accessToken);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: hashicorpVaultKeys.all });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { getHashicorpVaultConfig } from "@/components/networking";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import useAuthorized from "../useAuthorized";
|
||||
import { createQueryKeys } from "../common/queryKeysFactory";
|
||||
|
||||
const hashicorpVaultKeys = createQueryKeys("hashicorpVaultConfig");
|
||||
|
||||
export const useHashicorpVaultConfig = () => {
|
||||
const { accessToken } = useAuthorized();
|
||||
|
||||
return useQuery<Record<string, any>>({
|
||||
queryKey: hashicorpVaultKeys.list({}),
|
||||
queryFn: async () => {
|
||||
if (!accessToken) {
|
||||
throw new Error("Access token is required");
|
||||
}
|
||||
return getHashicorpVaultConfig(accessToken);
|
||||
},
|
||||
enabled: !!accessToken,
|
||||
staleTime: 60 * 60 * 1000,
|
||||
gcTime: 60 * 60 * 1000,
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { updateHashicorpVaultConfig } from "@/components/networking";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { createQueryKeys } from "../common/queryKeysFactory";
|
||||
|
||||
const hashicorpVaultKeys = createQueryKeys("hashicorpVaultConfig");
|
||||
|
||||
export const useUpdateHashicorpVaultConfig = (accessToken: string | null) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (config: Record<string, any>) => {
|
||||
if (!accessToken) {
|
||||
throw new Error("Access token is required");
|
||||
}
|
||||
return updateHashicorpVaultConfig(accessToken, config);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: hashicorpVaultKeys.all });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
"use client";
|
||||
|
||||
import { useHashicorpVaultConfig } from "@/app/(dashboard)/hooks/configOverrides/useHashicorpVaultConfig";
|
||||
import { useUpdateHashicorpVaultConfig } from "@/app/(dashboard)/hooks/configOverrides/useUpdateHashicorpVaultConfig";
|
||||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import NotificationManager from "@/components/molecules/notifications_manager";
|
||||
import { Button, Divider, Form, Input, Modal, Space, Typography } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { SENSITIVE_FIELDS, FIELD_LABELS } from "./constants";
|
||||
|
||||
interface FieldGroup {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
fields: string[];
|
||||
}
|
||||
|
||||
const FIELD_GROUPS: FieldGroup[] = [
|
||||
{
|
||||
title: "Connection",
|
||||
fields: ["vault_addr", "vault_namespace", "vault_mount_name", "vault_path_prefix"],
|
||||
},
|
||||
{
|
||||
title: "Token Authentication",
|
||||
subtitle: "Use a Vault token to authenticate. Only one auth method is required.",
|
||||
fields: ["vault_token"],
|
||||
},
|
||||
{
|
||||
title: "AppRole Authentication",
|
||||
subtitle: "Use AppRole credentials to authenticate. Only one auth method is required.",
|
||||
fields: ["approle_role_id", "approle_secret_id", "approle_mount_path"],
|
||||
},
|
||||
{
|
||||
title: "TLS",
|
||||
subtitle: "Optional client certificate for mTLS.",
|
||||
fields: ["client_cert", "client_key", "vault_cert_role"],
|
||||
},
|
||||
];
|
||||
|
||||
interface EditHashicorpVaultModalProps {
|
||||
isVisible: boolean;
|
||||
onCancel: () => void;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
const EditHashicorpVaultModal: React.FC<EditHashicorpVaultModalProps> = ({
|
||||
isVisible,
|
||||
onCancel,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const { accessToken } = useAuthorized();
|
||||
const { data } = useHashicorpVaultConfig();
|
||||
const { mutateAsync, isPending } = useUpdateHashicorpVaultConfig(accessToken);
|
||||
|
||||
const schema = data?.field_schema;
|
||||
const properties = schema?.properties ?? {};
|
||||
const rawValues = data?.values ?? {};
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible && data) {
|
||||
form.resetFields();
|
||||
// Only set non-sensitive fields — sensitive ones show as placeholders
|
||||
const formValues: Record<string, any> = {};
|
||||
for (const [key, value] of Object.entries(rawValues)) {
|
||||
if (!SENSITIVE_FIELDS.has(key)) {
|
||||
formValues[key] = value;
|
||||
}
|
||||
}
|
||||
form.setFieldsValue(formValues);
|
||||
}
|
||||
}, [isVisible, data, form]);
|
||||
|
||||
const handleSubmit = async (formValues: Record<string, any>) => {
|
||||
const config: Record<string, any> = {};
|
||||
for (const [key, value] of Object.entries(formValues)) {
|
||||
if (value !== undefined && value !== null && value !== "") {
|
||||
// Non-empty value → update
|
||||
config[key] = value;
|
||||
} else if (!SENSITIVE_FIELDS.has(key)) {
|
||||
// Non-sensitive field cleared → send "" to clear it on the backend
|
||||
config[key] = "";
|
||||
}
|
||||
// Sensitive field left blank → omit from payload (keep existing)
|
||||
}
|
||||
|
||||
await mutateAsync(config, {
|
||||
onSuccess: () => {
|
||||
NotificationManager.success("Hashicorp Vault configuration updated successfully");
|
||||
onSuccess();
|
||||
},
|
||||
onError: (err) => {
|
||||
NotificationManager.fromBackend(err);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
form.resetFields();
|
||||
onCancel();
|
||||
};
|
||||
|
||||
const renderField = (fieldName: string) => {
|
||||
const fieldSchema = properties[fieldName];
|
||||
if (!fieldSchema) return null;
|
||||
|
||||
const rules =
|
||||
fieldName === "vault_addr"
|
||||
? [{ pattern: /^https?:\/\/.+/, message: "Must start with http:// or https://" }]
|
||||
: undefined;
|
||||
|
||||
const isSensitive = SENSITIVE_FIELDS.has(fieldName);
|
||||
const existingValue = rawValues[fieldName];
|
||||
const hasExistingValue = isSensitive && existingValue != null && existingValue !== "";
|
||||
const placeholder = hasExistingValue
|
||||
? `Leave blank to keep existing (${existingValue})`
|
||||
: fieldSchema?.description;
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
key={fieldName}
|
||||
name={fieldName}
|
||||
label={FIELD_LABELS[fieldName] ?? fieldName}
|
||||
rules={rules}
|
||||
>
|
||||
{isSensitive ? (
|
||||
<Input.Password placeholder={placeholder} />
|
||||
) : (
|
||||
<Input placeholder={fieldSchema?.description} />
|
||||
)}
|
||||
</Form.Item>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Edit Hashicorp Vault Configuration"
|
||||
open={isVisible}
|
||||
width={700}
|
||||
footer={
|
||||
<Space>
|
||||
<Button onClick={handleCancel} disabled={isPending}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="primary" loading={isPending} onClick={() => form.submit()}>
|
||||
{isPending ? "Saving..." : "Save"}
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
||||
{FIELD_GROUPS.map((group, index) => (
|
||||
<div key={group.title}>
|
||||
{index > 0 && <Divider />}
|
||||
<Typography.Title level={5} style={{ marginBottom: 4 }}>
|
||||
{group.title}
|
||||
</Typography.Title>
|
||||
{group.subtitle && (
|
||||
<Typography.Paragraph type="secondary" style={{ marginBottom: 16 }}>
|
||||
{group.subtitle}
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
{group.fields.map(renderField)}
|
||||
</div>
|
||||
))}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditHashicorpVaultModal;
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useHashicorpVaultConfig } from "@/app/(dashboard)/hooks/configOverrides/useHashicorpVaultConfig";
|
||||
import { useDeleteHashicorpVaultConfig } from "@/app/(dashboard)/hooks/configOverrides/useDeleteHashicorpVaultConfig";
|
||||
import { useUpdateHashicorpVaultConfig } from "@/app/(dashboard)/hooks/configOverrides/useUpdateHashicorpVaultConfig";
|
||||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
|
||||
import NotificationManager from "@/components/molecules/notifications_manager";
|
||||
import { testHashicorpVaultConnection } from "@/components/networking";
|
||||
import { Alert, Button, Card, Descriptions, Skeleton, Space, Typography } from "antd";
|
||||
import { Edit, KeyRound, PlugZap, Trash2 } from "lucide-react";
|
||||
import { SENSITIVE_FIELDS, FIELD_LABELS } from "./constants";
|
||||
import EditHashicorpVaultModal from "./EditHashicorpVaultModal";
|
||||
import HashicorpVaultEmptyPlaceholder from "./HashicorpVaultEmptyPlaceholder";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
function detectAuthMethod(values: Record<string, any>): string {
|
||||
if (values.vault_token) return "Token";
|
||||
if (values.approle_role_id || values.approle_secret_id) return "AppRole";
|
||||
return "None";
|
||||
}
|
||||
|
||||
const descriptionsConfig = {
|
||||
column: { xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 },
|
||||
};
|
||||
|
||||
export default function HashicorpVault() {
|
||||
const { accessToken } = useAuthorized();
|
||||
const { data, isLoading, isError, error, refetch } = useHashicorpVaultConfig();
|
||||
const { mutate: deleteConfig, isPending: isDeleting } = useDeleteHashicorpVaultConfig(accessToken);
|
||||
const { mutateAsync: updateConfig } = useUpdateHashicorpVaultConfig(accessToken);
|
||||
|
||||
const [isEditModalVisible, setIsEditModalVisible] = useState(false);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [clearingField, setClearingField] = useState<string | null>(null);
|
||||
const [isClearingField, setIsClearingField] = useState(false);
|
||||
const [isTesting, setIsTesting] = useState(false);
|
||||
|
||||
const rawValues = data?.values ?? {};
|
||||
const isConfigured = Boolean(rawValues.vault_addr);
|
||||
|
||||
const handleTestConnection = async () => {
|
||||
if (!accessToken) return;
|
||||
setIsTesting(true);
|
||||
try {
|
||||
const result = await testHashicorpVaultConnection(accessToken);
|
||||
NotificationManager.success(result.message || "Connection to Vault successful!");
|
||||
} catch (err) {
|
||||
NotificationManager.fromBackend(err);
|
||||
} finally {
|
||||
setIsTesting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteConfig(undefined, {
|
||||
onSuccess: () => {
|
||||
NotificationManager.success("Hashicorp Vault configuration deleted");
|
||||
setIsDeleteModalOpen(false);
|
||||
},
|
||||
onError: (err) => {
|
||||
NotificationManager.fromBackend(err);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleClearField = async () => {
|
||||
if (!clearingField) return;
|
||||
setIsClearingField(true);
|
||||
try {
|
||||
await updateConfig({ [clearingField]: "" });
|
||||
NotificationManager.success(`${FIELD_LABELS[clearingField] ?? clearingField} cleared`);
|
||||
setClearingField(null);
|
||||
refetch();
|
||||
} catch (err) {
|
||||
NotificationManager.fromBackend(err);
|
||||
} finally {
|
||||
setIsClearingField(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderValue = (key: string) => {
|
||||
const value = rawValues[key];
|
||||
if (!value) {
|
||||
return <span className="text-gray-400 italic">Not configured</span>;
|
||||
}
|
||||
if (SENSITIVE_FIELDS.has(key)) {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<Text className="font-mono text-gray-600">{value}</Text>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
danger
|
||||
icon={<Trash2 className="w-3.5 h-3.5" />}
|
||||
onClick={() => setClearingField(key)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <Text className="font-mono text-gray-600">{value}</Text>;
|
||||
};
|
||||
|
||||
const renderSettings = () => {
|
||||
// Only show fields that have values, plus auth method
|
||||
const fieldsToShow = Object.entries(rawValues).filter(
|
||||
([_, value]) => value != null && value !== ""
|
||||
);
|
||||
|
||||
if (fieldsToShow.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Descriptions bordered {...descriptionsConfig}>
|
||||
<Descriptions.Item label="Auth Method">
|
||||
<Text>{detectAuthMethod(rawValues)}</Text>
|
||||
</Descriptions.Item>
|
||||
{fieldsToShow.map(([key]) => (
|
||||
<Descriptions.Item key={key} label={FIELD_LABELS[key] ?? key}>
|
||||
{renderValue(key)}
|
||||
</Descriptions.Item>
|
||||
))}
|
||||
</Descriptions>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading ? (
|
||||
<Card>
|
||||
<Skeleton active />
|
||||
</Card>
|
||||
) : isError ? (
|
||||
<Card>
|
||||
<Alert
|
||||
type="error"
|
||||
message="Could not load Hashicorp Vault configuration"
|
||||
description={error instanceof Error ? error.message : undefined}
|
||||
/>
|
||||
</Card>
|
||||
) : (
|
||||
<Space direction="vertical" size="large" className="w-full">
|
||||
<Card>
|
||||
<Space direction="vertical" size="large" className="w-full">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<KeyRound className="w-6 h-6 text-gray-400" />
|
||||
<div>
|
||||
<Title level={3} style={{ marginBottom: 0 }}>Hashicorp Vault</Title>
|
||||
<Text type="secondary">Manage secret manager configuration</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
{isConfigured && (
|
||||
<>
|
||||
<Button
|
||||
icon={<PlugZap className="w-4 h-4" />}
|
||||
loading={isTesting}
|
||||
onClick={handleTestConnection}
|
||||
>
|
||||
Test Connection
|
||||
</Button>
|
||||
<Button
|
||||
icon={<Edit className="w-4 h-4" />}
|
||||
onClick={() => setIsEditModalVisible(true)}
|
||||
>
|
||||
Edit Configuration
|
||||
</Button>
|
||||
<Button
|
||||
danger
|
||||
icon={<Trash2 className="w-4 h-4" />}
|
||||
onClick={() => setIsDeleteModalOpen(true)}
|
||||
>
|
||||
Delete Configuration
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isConfigured && (
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message="Secrets must be stored with the field name "key""
|
||||
description={
|
||||
<>
|
||||
<Text code>vault kv put secret/SECRET_NAME key=secret_value</Text>
|
||||
<br />
|
||||
<Typography.Link
|
||||
href="https://docs.litellm.ai/docs/secret_managers/hashicorp_vault"
|
||||
target="_blank"
|
||||
>
|
||||
View documentation
|
||||
</Typography.Link>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isConfigured ? (
|
||||
renderSettings()
|
||||
) : (
|
||||
<HashicorpVaultEmptyPlaceholder onAdd={() => setIsEditModalVisible(true)} />
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
</Space>
|
||||
)}
|
||||
|
||||
<EditHashicorpVaultModal
|
||||
isVisible={isEditModalVisible}
|
||||
onCancel={() => setIsEditModalVisible(false)}
|
||||
onSuccess={() => {
|
||||
setIsEditModalVisible(false);
|
||||
refetch();
|
||||
}}
|
||||
/>
|
||||
|
||||
<DeleteResourceModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
title="Delete Hashicorp Vault Configuration?"
|
||||
message="Models using Vault secrets will lose access to their API keys until a new configuration is saved."
|
||||
resourceInformationTitle="Vault Configuration"
|
||||
resourceInformation={[
|
||||
{ label: "Vault Address", value: rawValues.vault_addr },
|
||||
]}
|
||||
onCancel={() => setIsDeleteModalOpen(false)}
|
||||
onOk={handleDelete}
|
||||
confirmLoading={isDeleting}
|
||||
/>
|
||||
|
||||
<DeleteResourceModal
|
||||
isOpen={clearingField !== null}
|
||||
title={`Clear ${clearingField ? (FIELD_LABELS[clearingField] ?? clearingField) : ""}?`}
|
||||
message="This will remove the stored value."
|
||||
resourceInformationTitle="Field"
|
||||
resourceInformation={[
|
||||
{ label: "Field", value: clearingField ? (FIELD_LABELS[clearingField] ?? clearingField) : "" },
|
||||
]}
|
||||
onCancel={() => setClearingField(null)}
|
||||
onOk={handleClearField}
|
||||
confirmLoading={isClearingField}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import { Empty, Typography, Button } from "antd";
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
|
||||
interface HashicorpVaultEmptyPlaceholderProps {
|
||||
onAdd: () => void;
|
||||
}
|
||||
|
||||
export default function HashicorpVaultEmptyPlaceholder({ onAdd }: HashicorpVaultEmptyPlaceholderProps) {
|
||||
return (
|
||||
<div className="bg-white p-12 rounded-lg border border-dashed border-gray-300 text-center w-full">
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description={
|
||||
<div className="space-y-2">
|
||||
<Title level={4}>No Vault Configuration Found</Title>
|
||||
<Paragraph type="secondary" className="max-w-md mx-auto">
|
||||
Configure Hashicorp Vault to securely manage provider API keys and secrets
|
||||
for your LiteLLM deployment.
|
||||
</Paragraph>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Button type="primary" size="large" onClick={onAdd} className="flex items-center gap-2 mx-auto mt-4">
|
||||
Configure Vault
|
||||
</Button>
|
||||
</Empty>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
export const SENSITIVE_FIELDS = new Set([
|
||||
"vault_token",
|
||||
"approle_role_id",
|
||||
"approle_secret_id",
|
||||
"client_key",
|
||||
]);
|
||||
|
||||
export const FIELD_LABELS: Record<string, string> = {
|
||||
vault_addr: "Vault Address",
|
||||
vault_namespace: "Namespace",
|
||||
vault_mount_name: "KV Mount Name",
|
||||
vault_path_prefix: "Path Prefix",
|
||||
vault_token: "Token",
|
||||
approle_role_id: "Role ID",
|
||||
approle_secret_id: "Secret ID",
|
||||
approle_mount_path: "Mount Path",
|
||||
client_cert: "Client Certificate",
|
||||
client_key: "Client Key",
|
||||
vault_cert_role: "Certificate Role",
|
||||
};
|
||||
|
|
@ -9659,6 +9659,95 @@ export const updateUiSettings = async (accessToken: string, settings: Record<str
|
|||
return data;
|
||||
};
|
||||
|
||||
export const getHashicorpVaultConfig = async (accessToken: string) => {
|
||||
const proxyBaseUrl = getProxyBaseUrl();
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/config_overrides/hashicorp_vault`
|
||||
: `/config_overrides/hashicorp_vault`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const detail = errorData?.detail;
|
||||
const errorMessage =
|
||||
(typeof detail === "object" && detail?.error) ||
|
||||
(typeof detail === "string" && detail) ||
|
||||
deriveErrorMessage(errorData);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateHashicorpVaultConfig = async (
|
||||
accessToken: string,
|
||||
config: Record<string, any>,
|
||||
) => {
|
||||
const proxyBaseUrl = getProxyBaseUrl();
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/config_overrides/hashicorp_vault`
|
||||
: `/config_overrides/hashicorp_vault`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(config),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteHashicorpVaultConfig = async (accessToken: string) => {
|
||||
const proxyBaseUrl = getProxyBaseUrl();
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/config_overrides/hashicorp_vault`
|
||||
: `/config_overrides/hashicorp_vault`;
|
||||
const response = await fetch(url, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
export const testHashicorpVaultConnection = async (accessToken: string) => {
|
||||
const proxyBaseUrl = getProxyBaseUrl();
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/config_overrides/hashicorp_vault/test_connection`
|
||||
: `/config_overrides/hashicorp_vault/test_connection`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Claude Code Marketplace Networking Functions
|
||||
// ============================================================
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue