Add SSH key generation and deployment features (#234)
* 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> * Add comprehensive SSH key management and validation features - Add support for both private and public key storage - Implement automatic SSH key type detection for all major formats (RSA, Ed25519, ECDSA, DSA) - Add real-time key pair validation to verify private/public key correspondence - Enhance credential editor UI with unified key input interface supporting upload/paste - Improve file format support including extensionless files (id_rsa, id_ed25519, etc.) - Add comprehensive fallback detection for OpenSSH format keys - Implement debounced API calls for better UX during real-time validation - Update database schema with backward compatibility for existing credentials - Add API endpoints for key detection and pair validation - Fix SSH2 module integration issues in TypeScript environment 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Optimize credentials interface and add i18n improvements - Merge upload/paste tabs into unified SSH key input interface - Remove manual key type selection dropdown (rely on auto-detection) - Add public key generation from private key functionality - Complete key pair validation removal to fix errors - Add missing translation keys for better internationalization - Improve UX with streamlined credential editing workflow * Implement direct SSH key generation with ssh2 native API - Replace complex PEM-to-SSH conversion logic with ssh2's generateKeyPairSync - Add three key generation buttons: Ed25519, ECDSA P-256, and RSA - Generate keys directly in SSH format (ssh-ed25519, ecdsa-sha2-nistp256, ssh-rsa) - Fix ECDSA parameter bug: use bits (256) instead of curve for ssh2 API - Enhance generate-public-key endpoint with SSH format conversion - Add comprehensive key type detection and parsing fallbacks - Add internationalization support for key generation UI - Simplify codebase from 300+ lines to ~80 lines of clean SSH generation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Add passphrase support for SSH key generation - Add optional passphrase input field in key generation container - Implement AES-128-CBC encryption for protected private keys - Auto-fill key password field when passphrase is provided - Support passphrase protection for all key types (Ed25519, ECDSA, RSA) - Enhance user experience with automatic form field population * Implement SSH key deployment feature with credential resolution - Add SSH key deployment endpoint supporting all authentication types - Implement automatic credential resolution for credential-based hosts - Add deployment UI with host selection and progress tracking - Support password, key, and credential authentication methods - Include deployment verification and error handling - Add public key field to credential types and API responses - Implement secure SSH connection handling with proper timeout 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
This commit was merged in pull request #234.
This commit is contained in:
@@ -1506,7 +1506,7 @@ export async function getCredentials(): Promise<any> {
|
||||
const response = await authApi.get("/credentials");
|
||||
return response.data;
|
||||
} 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}`);
|
||||
return response.data;
|
||||
} 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);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
handleApiError(error, "create credential");
|
||||
throw handleApiError(error, "create credential");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1539,7 +1539,7 @@ export async function updateCredential(
|
||||
);
|
||||
return response.data;
|
||||
} 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}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
handleApiError(error, "delete credential");
|
||||
throw handleApiError(error, "delete credential");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1594,7 +1594,7 @@ export async function applyCredentialToHost(
|
||||
);
|
||||
return response.data;
|
||||
} 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`);
|
||||
return response.data;
|
||||
} 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;
|
||||
} catch (error) {
|
||||
handleApiError(error, "migrate host to credential");
|
||||
throw handleApiError(error, "migrate host to credential");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1663,6 +1663,98 @@ export async function renameCredentialFolder(
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
handleApiError(error, "rename credential folder");
|
||||
throw handleApiError(error, "rename credential folder");
|
||||
}
|
||||
}
|
||||
|
||||
export async function detectKeyType(
|
||||
privateKey: string,
|
||||
keyPassword?: string,
|
||||
): Promise<any> {
|
||||
try {
|
||||
const response = await authApi.post("/credentials/detect-key-type", {
|
||||
privateKey,
|
||||
keyPassword,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "detect key type");
|
||||
}
|
||||
}
|
||||
|
||||
export async function detectPublicKeyType(
|
||||
publicKey: string,
|
||||
): Promise<any> {
|
||||
try {
|
||||
const response = await authApi.post("/credentials/detect-public-key-type", {
|
||||
publicKey,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "detect public key type");
|
||||
}
|
||||
}
|
||||
|
||||
export async function validateKeyPair(
|
||||
privateKey: string,
|
||||
publicKey: string,
|
||||
keyPassword?: string,
|
||||
): Promise<any> {
|
||||
try {
|
||||
const response = await authApi.post("/credentials/validate-key-pair", {
|
||||
privateKey,
|
||||
publicKey,
|
||||
keyPassword,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "validate key pair");
|
||||
}
|
||||
}
|
||||
|
||||
export async function generatePublicKeyFromPrivate(
|
||||
privateKey: string,
|
||||
keyPassword?: string,
|
||||
): Promise<any> {
|
||||
try {
|
||||
const response = await authApi.post("/credentials/generate-public-key", {
|
||||
privateKey,
|
||||
keyPassword,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "generate public key from private key");
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateKeyPair(
|
||||
keyType: 'ssh-ed25519' | 'ssh-rsa' | 'ecdsa-sha2-nistp256',
|
||||
keySize?: number,
|
||||
passphrase?: string,
|
||||
): Promise<any> {
|
||||
try {
|
||||
const response = await authApi.post("/credentials/generate-key-pair", {
|
||||
keyType,
|
||||
keySize,
|
||||
passphrase,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "generate SSH key pair");
|
||||
}
|
||||
}
|
||||
|
||||
export async function deployCredentialToHost(
|
||||
credentialId: number,
|
||||
targetHostId: number,
|
||||
): Promise<any> {
|
||||
try {
|
||||
const response = await authApi.post(
|
||||
`/credentials/${credentialId}/deploy-to-host`,
|
||||
{ targetHostId }
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleApiError(error, "deploy credential to host");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user