mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
fix(frontend): add force-delete flow and try/catch for credential operations
- DeleteConfirmationModal now shows backend warning message and offers "Force Delete" when API returns need_confirmation instead of just a toast (mirrors integrations page pattern) - HostScopedCredentialsModal onSubmit delete-then-create is now wrapped in try/catch to prevent silent credential loss on creation failure
This commit is contained in:
@@ -94,6 +94,7 @@ export function CredentialsInput({
|
||||
handleDeleteCredential,
|
||||
handleDeleteConfirm,
|
||||
credentialToDelete,
|
||||
deleteWarningMessage,
|
||||
setCredentialToDelete,
|
||||
isDeletingCredential,
|
||||
} = hookData;
|
||||
@@ -202,9 +203,11 @@ export function CredentialsInput({
|
||||
|
||||
<DeleteConfirmationModal
|
||||
credentialToDelete={credentialToDelete}
|
||||
warningMessage={deleteWarningMessage}
|
||||
isDeleting={isDeletingCredential}
|
||||
onClose={() => setCredentialToDelete(null)}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
onForceConfirm={() => handleDeleteConfirm(true)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -4,16 +4,20 @@ import { Dialog } from "@/components/molecules/Dialog/Dialog";
|
||||
|
||||
interface Props {
|
||||
credentialToDelete: { id: string; title: string } | null;
|
||||
warningMessage?: string | null;
|
||||
isDeleting: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
onForceConfirm: () => void;
|
||||
}
|
||||
|
||||
export function DeleteConfirmationModal({
|
||||
credentialToDelete,
|
||||
warningMessage,
|
||||
isDeleting,
|
||||
onClose,
|
||||
onConfirm,
|
||||
onForceConfirm,
|
||||
}: Props) {
|
||||
return (
|
||||
<Dialog
|
||||
@@ -27,21 +31,35 @@ export function DeleteConfirmationModal({
|
||||
styling={{ maxWidth: "32rem" }}
|
||||
>
|
||||
<Dialog.Content>
|
||||
<Text variant="large">
|
||||
Are you sure you want to delete "{credentialToDelete?.title}
|
||||
"? This action cannot be undone.
|
||||
</Text>
|
||||
{warningMessage ? (
|
||||
<Text variant="large">{warningMessage}</Text>
|
||||
) : (
|
||||
<Text variant="large">
|
||||
Are you sure you want to delete "{credentialToDelete?.title}
|
||||
"? This action cannot be undone.
|
||||
</Text>
|
||||
)}
|
||||
<Dialog.Footer>
|
||||
<Button variant="secondary" onClick={onClose} disabled={isDeleting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={onConfirm}
|
||||
loading={isDeleting}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
{warningMessage ? (
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={onForceConfirm}
|
||||
loading={isDeleting}
|
||||
>
|
||||
Force Delete
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={onConfirm}
|
||||
loading={isDeleting}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { CredentialsProvidersContext } from "@/providers/agent-credentials/credentials-provider";
|
||||
import { getHostFromUrl } from "@/lib/utils/url";
|
||||
import { PlusIcon, TrashIcon } from "@phosphor-icons/react";
|
||||
import { toast } from "@/components/molecules/Toast/use-toast";
|
||||
|
||||
type Props = {
|
||||
schema: BlockIOCredentialsSubSchema;
|
||||
@@ -149,22 +150,33 @@ export function HostScopedCredentialsModal({
|
||||
const existingForHost = allProviderCredentials.filter(
|
||||
(c) => c.type === "host_scoped" && "host" in c && c.host === host,
|
||||
);
|
||||
for (const existing of existingForHost) {
|
||||
await deleteCredentials(existing.id, true);
|
||||
|
||||
try {
|
||||
for (const existing of existingForHost) {
|
||||
await deleteCredentials(existing.id, true);
|
||||
}
|
||||
|
||||
const newCredentials = await createHostScopedCredentials({
|
||||
host,
|
||||
title: currentHost || host,
|
||||
headers,
|
||||
});
|
||||
|
||||
onCredentialsCreate({
|
||||
provider,
|
||||
id: newCredentials.id,
|
||||
type: "host_scoped",
|
||||
title: newCredentials.title,
|
||||
});
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error ? error.message : "Something went wrong";
|
||||
toast({
|
||||
title: "Failed to save credentials",
|
||||
description: message,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
|
||||
const newCredentials = await createHostScopedCredentials({
|
||||
host,
|
||||
title: currentHost || host,
|
||||
headers,
|
||||
});
|
||||
|
||||
onCredentialsCreate({
|
||||
provider,
|
||||
id: newCredentials.id,
|
||||
type: "host_scoped",
|
||||
title: newCredentials.title,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -58,6 +58,9 @@ export function useCredentialsInput({
|
||||
id: string;
|
||||
title: string;
|
||||
} | null>(null);
|
||||
const [deleteWarningMessage, setDeleteWarningMessage] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
|
||||
const api = useBackendAPI();
|
||||
const credentials = useCredentials(schema, siblingInputs);
|
||||
@@ -297,6 +300,7 @@ export function useCredentialsInput({
|
||||
}
|
||||
|
||||
function handleDeleteCredential(credential: { id: string; title: string }) {
|
||||
setDeleteWarningMessage(null);
|
||||
setCredentialToDelete(credential);
|
||||
}
|
||||
|
||||
@@ -319,14 +323,12 @@ export function useCredentialsInput({
|
||||
if (selectedCredential?.id === credentialToDelete.id) {
|
||||
onSelectCredential(undefined);
|
||||
}
|
||||
setDeleteWarningMessage(null);
|
||||
setCredentialToDelete(null);
|
||||
} else if ("need_confirmation" in result && result.need_confirmation) {
|
||||
toast({
|
||||
title: "Credential is in use",
|
||||
description: result.message,
|
||||
variant: "destructive",
|
||||
duration: 8000,
|
||||
});
|
||||
setDeleteWarningMessage(
|
||||
result.message || "This credential is in use. Force delete?",
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
const message =
|
||||
@@ -364,6 +366,7 @@ export function useCredentialsInput({
|
||||
isOAuth2FlowInProgress,
|
||||
cancelOAuthFlow,
|
||||
credentialToDelete,
|
||||
deleteWarningMessage,
|
||||
isDeletingCredential,
|
||||
actionButtonText: getActionButtonText(
|
||||
supportsOAuth2,
|
||||
|
||||
Reference in New Issue
Block a user