From 6fd7fc03007d08cb03ececb00653f1e7a3665bb4 Mon Sep 17 00:00:00 2001 From: Jamie Banks Date: Tue, 9 Jun 2026 15:41:31 +0100 Subject: [PATCH 1/2] feat: add visual drag-and-drop reordering indicators and improve event handling for connection items --- frontend/src/main.js | 12 +++++++++++- frontend/src/style.css | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/frontend/src/main.js b/frontend/src/main.js index 15a944e..ea53af4 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -91,12 +91,22 @@ async function loadConnections() { draggedConnId = null; el.style.opacity = '1'; }); + el.addEventListener('dragenter', (e) => { + e.preventDefault(); + if (draggedConnId && draggedConnId !== c.id) { + el.style.borderTop = '2px solid var(--accent)'; + } + }); + el.addEventListener('dragleave', (e) => { + el.style.borderTop = ''; + }); el.addEventListener('dragover', (e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }); el.addEventListener('drop', async (e) => { e.preventDefault(); + el.style.borderTop = ''; if (!draggedConnId || draggedConnId === c.id) return; const ids = connectionsCache.map(conn => conn.id); @@ -109,10 +119,10 @@ async function loadConnections() { try { await ReorderConnections(ids); - await loadConnections(); } catch (err) { console.error("Failed to reorder connections:", err); } + await loadConnections(); } }); diff --git a/frontend/src/style.css b/frontend/src/style.css index 1472b6d..97a8f82 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -57,6 +57,14 @@ body { transition: background 0.2s; } +.conn-item * { + pointer-events: none; +} + +.conn-item .edit-btn { + pointer-events: auto; +} + .conn-item:hover { background: var(--surface-hover); } From 24331ac3d43fad0a7a8e0d2c5d597527d24fbe08 Mon Sep 17 00:00:00 2001 From: Jamie Banks Date: Tue, 9 Jun 2026 15:47:12 +0100 Subject: [PATCH 2/2] feat: add keyboard-interactive authentication support for SFTP to enable interactive prompts --- internal/protocols/sftp/sftp.go | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/internal/protocols/sftp/sftp.go b/internal/protocols/sftp/sftp.go index df1eadc..4d04d71 100644 --- a/internal/protocols/sftp/sftp.go +++ b/internal/protocols/sftp/sftp.go @@ -32,7 +32,7 @@ func (e *SFTPExplorer) Connect() error { port = 22 } - var authMethod ssh.AuthMethod + var authMethods []ssh.AuthMethod secretStr := strings.TrimSpace(e.secret) // Handle case where newlines might be escaped by mistake @@ -43,16 +43,35 @@ func (e *SFTPExplorer) Connect() error { if err != nil { return fmt.Errorf("failed to parse private key: %w", err) } - authMethod = ssh.PublicKeys(signer) + authMethods = append(authMethods, ssh.PublicKeys(signer)) } else { - authMethod = ssh.Password(e.secret) + authMethods = append(authMethods, ssh.Password(e.secret)) } + // Always add KeyboardInteractive to support Duo MFA and servers that require interactive auth. + authMethods = append(authMethods, ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) (answers []string, err error) { + answers = make([]string, len(questions)) + for i, q := range questions { + qLower := strings.ToLower(q) + if strings.Contains(secretStr, "-----BEGIN") { + // If using a private key, we don't have a password. + // If it's a Duo prompt asking for an option, "1" is typically Duo Push. + if strings.Contains(qLower, "duo push") || strings.Contains(qLower, "passcode or option") { + answers[i] = "1" + } else { + answers[i] = "" + } + } else { + // Otherwise, respond with the password to keyboard interactive prompts + answers[i] = e.secret + } + } + return answers, nil + })) + config := &ssh.ClientConfig{ User: e.cfg.Username, - Auth: []ssh.AuthMethod{ - authMethod, - }, + Auth: authMethods, HostKeyCallback: ssh.InsecureIgnoreHostKey(), }