Fix SSH key upload and credential editing issues
Fixed two major credential management issues: 1. Fix SSH key upload button not responding (Issue #232) - Error handling was silently swallowing exceptions - Added proper error propagation in axios functions - Improved error display to show specific error messages - Users now see actual error details instead of generic messages 2. Improve credential editing to show actual content - Both "Upload File" and "Paste Key" modes now display existing data - Upload mode: shows current key content in read-only preview area - Paste mode: shows editable key content in textarea - Smart input method switching preserves existing data - Enhanced button labels and status indicators Key changes: - Fixed handleApiError propagation in main-axios.ts credential functions - Enhanced CredentialEditor.tsx with key content preview - Improved error handling with console logging for debugging - Better UX with clear status indicators and preserved data These fixes resolve the "Add Credential button does nothing" issue and provide full visibility of credential content during editing. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -176,7 +176,7 @@ export function CredentialEditor({
|
|||||||
if (defaultAuthType === "password") {
|
if (defaultAuthType === "password") {
|
||||||
formData.password = fullCredentialDetails.password || "";
|
formData.password = fullCredentialDetails.password || "";
|
||||||
} else if (defaultAuthType === "key") {
|
} else if (defaultAuthType === "key") {
|
||||||
formData.key = "existing_key";
|
formData.key = fullCredentialDetails.key || "";
|
||||||
formData.keyPassword = fullCredentialDetails.keyPassword || "";
|
formData.keyPassword = fullCredentialDetails.keyPassword || "";
|
||||||
formData.keyType =
|
formData.keyType =
|
||||||
(fullCredentialDetails.keyType as any) || ("auto" as const);
|
(fullCredentialDetails.keyType as any) || ("auto" as const);
|
||||||
@@ -230,8 +230,6 @@ export function CredentialEditor({
|
|||||||
if (data.key instanceof File) {
|
if (data.key instanceof File) {
|
||||||
const keyContent = await data.key.text();
|
const keyContent = await data.key.text();
|
||||||
submitData.key = keyContent;
|
submitData.key = keyContent;
|
||||||
} else if (data.key === "existing_key") {
|
|
||||||
delete submitData.key;
|
|
||||||
} else {
|
} else {
|
||||||
submitData.key = data.key;
|
submitData.key = data.key;
|
||||||
}
|
}
|
||||||
@@ -259,8 +257,13 @@ export function CredentialEditor({
|
|||||||
|
|
||||||
form.reset();
|
form.reset();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("Credential save error:", error);
|
||||||
|
if (error instanceof Error) {
|
||||||
|
toast.error(error.message);
|
||||||
|
} else {
|
||||||
toast.error(t("credentials.failedToSaveCredential"));
|
toast.error(t("credentials.failedToSaveCredential"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const [tagInput, setTagInput] = useState("");
|
const [tagInput, setTagInput] = useState("");
|
||||||
@@ -593,11 +596,23 @@ export function CredentialEditor({
|
|||||||
value={keyInputMethod}
|
value={keyInputMethod}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
setKeyInputMethod(value as "upload" | "paste");
|
setKeyInputMethod(value as "upload" | "paste");
|
||||||
|
// Only reset key value if we're not editing an existing credential
|
||||||
|
if (!editingCredential) {
|
||||||
if (value === "upload") {
|
if (value === "upload") {
|
||||||
form.setValue("key", null);
|
form.setValue("key", null);
|
||||||
} else {
|
} else {
|
||||||
form.setValue("key", "");
|
form.setValue("key", "");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// For existing credentials, preserve the key data when switching methods
|
||||||
|
const currentKey = fullCredentialDetails?.key || "";
|
||||||
|
if (value === "paste") {
|
||||||
|
form.setValue("key", currentKey);
|
||||||
|
} else {
|
||||||
|
// For upload mode, keep the current string value to show "existing key" status
|
||||||
|
form.setValue("key", currentKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
@@ -642,12 +657,12 @@ export function CredentialEditor({
|
|||||||
t("credentials.upload")
|
t("credentials.upload")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{field.value === "existing_key"
|
{field.value instanceof File
|
||||||
? t("hosts.existingKey")
|
? field.value.name
|
||||||
|
: typeof field.value === "string" && field.value && editingCredential
|
||||||
|
? t("hosts.existingKey") + " - " + t("credentials.updateKey")
|
||||||
: field.value
|
: field.value
|
||||||
? editingCredential
|
|
||||||
? t("credentials.updateKey")
|
? t("credentials.updateKey")
|
||||||
: field.value.name
|
|
||||||
: t("credentials.upload")}
|
: t("credentials.upload")}
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -656,6 +671,22 @@ export function CredentialEditor({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{/* Show existing key content preview for upload mode */}
|
||||||
|
{editingCredential && fullCredentialDetails?.key && typeof form.watch("key") === "string" && (
|
||||||
|
<FormItem className="mb-4">
|
||||||
|
<FormLabel>{t("credentials.sshPrivateKey")} ({t("hosts.existingKey")})</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<textarea
|
||||||
|
readOnly
|
||||||
|
className="flex min-h-[120px] w-full rounded-md border border-input bg-muted px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
value={fullCredentialDetails.key}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="text-xs text-muted-foreground mt-1">
|
||||||
|
Current SSH key content - {t("credentials.uploadFile")} to replace
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
<div className="grid grid-cols-15 gap-4 mt-4">
|
<div className="grid grid-cols-15 gap-4 mt-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
|||||||
@@ -1506,7 +1506,7 @@ export async function getCredentials(): Promise<any> {
|
|||||||
const response = await authApi.get("/credentials");
|
const response = await authApi.get("/credentials");
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleApiError(error, "fetch credentials");
|
throw handleApiError(error, "fetch credentials");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1515,7 +1515,7 @@ export async function getCredentialDetails(credentialId: number): Promise<any> {
|
|||||||
const response = await authApi.get(`/credentials/${credentialId}`);
|
const response = await authApi.get(`/credentials/${credentialId}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleApiError(error, "fetch credential details");
|
throw handleApiError(error, "fetch credential details");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1524,7 +1524,7 @@ export async function createCredential(credentialData: any): Promise<any> {
|
|||||||
const response = await authApi.post("/credentials", credentialData);
|
const response = await authApi.post("/credentials", credentialData);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleApiError(error, "create credential");
|
throw handleApiError(error, "create credential");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1539,7 +1539,7 @@ export async function updateCredential(
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleApiError(error, "update credential");
|
throw handleApiError(error, "update credential");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1548,7 +1548,7 @@ export async function deleteCredential(credentialId: number): Promise<any> {
|
|||||||
const response = await authApi.delete(`/credentials/${credentialId}`);
|
const response = await authApi.delete(`/credentials/${credentialId}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleApiError(error, "delete credential");
|
throw handleApiError(error, "delete credential");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1594,7 +1594,7 @@ export async function applyCredentialToHost(
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleApiError(error, "apply credential to host");
|
throw handleApiError(error, "apply credential to host");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1604,7 +1604,7 @@ export async function removeCredentialFromHost(hostId: number): Promise<any> {
|
|||||||
const response = await sshHostApi.delete(`/db/host/${hostId}/credential`);
|
const response = await sshHostApi.delete(`/db/host/${hostId}/credential`);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleApiError(error, "remove credential from host");
|
throw handleApiError(error, "remove credential from host");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1620,7 +1620,7 @@ export async function migrateHostToCredential(
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleApiError(error, "migrate host to credential");
|
throw handleApiError(error, "migrate host to credential");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1663,6 +1663,6 @@ export async function renameCredentialFolder(
|
|||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleApiError(error, "rename credential folder");
|
throw handleApiError(error, "rename credential folder");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user