diff --git a/frontend/src/main.js b/frontend/src/main.js
index 50b03d1..15a944e 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -1,4 +1,4 @@
-import { GetVersion, ListDir, GetConnections, SaveConnection, DeleteConnection, Delete, Rename, PromptUploadFiles, PromptUploadDirectory, PromptDownload, TransferItems, GetTransfers, ClearTransfers } from '../wailsjs/go/main/App.js';
+import { GetVersion, ListDir, GetConnections, SaveConnection, DeleteConnection, Delete, Rename, PromptUploadFiles, PromptUploadDirectory, PromptDownload, TransferItems, GetTransfers, ClearTransfers, ReorderConnections } from '../wailsjs/go/main/App.js';
let currentConn = 'local';
let currentPath = '';
@@ -8,6 +8,7 @@ let lastSelectedPath = null;
let showHiddenFiles = false;
let sortField = 'name';
let sortAsc = true;
+let draggedConnId = null;
function uuidv4() {
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
@@ -79,6 +80,42 @@ async function loadConnections() {
el.className = `conn-item ${currentConn === c.id ? 'active' : ''}`;
el.innerHTML = `
${c.protocol} ${c.name} `;
el.onclick = () => switchConn(c.id);
+
+ el.draggable = true;
+ el.addEventListener('dragstart', (e) => {
+ draggedConnId = c.id;
+ e.dataTransfer.effectAllowed = 'move';
+ el.style.opacity = '0.5';
+ });
+ el.addEventListener('dragend', () => {
+ draggedConnId = null;
+ el.style.opacity = '1';
+ });
+ el.addEventListener('dragover', (e) => {
+ e.preventDefault();
+ e.dataTransfer.dropEffect = 'move';
+ });
+ el.addEventListener('drop', async (e) => {
+ e.preventDefault();
+ if (!draggedConnId || draggedConnId === c.id) return;
+
+ const ids = connectionsCache.map(conn => conn.id);
+ const draggedIdx = ids.indexOf(draggedConnId);
+ const targetIdx = ids.indexOf(c.id);
+
+ if (draggedIdx !== -1 && targetIdx !== -1) {
+ ids.splice(draggedIdx, 1);
+ ids.splice(targetIdx, 0, draggedConnId);
+
+ try {
+ await ReorderConnections(ids);
+ await loadConnections();
+ } catch (err) {
+ console.error("Failed to reorder connections:", err);
+ }
+ }
+ });
+
list.appendChild(el);
});
}
@@ -137,9 +174,27 @@ window.closeConnModal = () => {
window.updateProtocolFields = () => {
const protocol = document.getElementById('conn-protocol').value;
- const showBucket = protocol === 's3' || protocol === 'smb';
+ const showBucket = protocol === 's3' || protocol === 'smb' || protocol === 'nfs';
document.getElementById('bucket-fields').style.display = showBucket ? 'block' : 'none';
+ const bucketLabel = document.getElementById('bucket-label');
+ if (bucketLabel) {
+ if (protocol === 'nfs') {
+ bucketLabel.innerText = 'Export Path';
+ } else if (protocol === 'smb') {
+ bucketLabel.innerText = 'Share';
+ } else {
+ bucketLabel.innerText = 'Bucket';
+ }
+ }
+
+ const isNFS = protocol === 'nfs';
+ const portGroup = document.getElementById('port-group');
+ if (portGroup) portGroup.style.display = isNFS ? 'none' : 'block';
+
+ const authRow = document.getElementById('auth-row');
+ if (authRow) authRow.style.display = isNFS ? 'none' : 'flex';
+
// Only show S3 specific fields when S3 is selected
const showS3Specific = protocol === 's3';
document.getElementById('region-group').style.display = showS3Specific ? 'flex' : 'none';
@@ -427,8 +482,19 @@ document.addEventListener('contextmenu', (e) => {
document.getElementById('ctx-divider').style.display = 'block';
menu.style.display = 'block';
- menu.style.left = `${e.pageX}px`;
- menu.style.top = `${e.pageY}px`;
+
+ let left = e.pageX;
+ let top = e.pageY;
+
+ if (left + menu.offsetWidth > window.innerWidth) {
+ left = window.innerWidth - menu.offsetWidth;
+ }
+ if (top + menu.offsetHeight > window.innerHeight) {
+ top = window.innerHeight - menu.offsetHeight;
+ }
+
+ menu.style.left = `${left}px`;
+ menu.style.top = `${top}px`;
} else if (isBrowserPane && !e.target.closest('#transfer-dest-list')) {
e.preventDefault();
@@ -439,8 +505,19 @@ document.addEventListener('contextmenu', (e) => {
document.getElementById('ctx-divider').style.display = 'none';
menu.style.display = 'block';
- menu.style.left = `${e.pageX}px`;
- menu.style.top = `${e.pageY}px`;
+
+ let left = e.pageX;
+ let top = e.pageY;
+
+ if (left + menu.offsetWidth > window.innerWidth) {
+ left = window.innerWidth - menu.offsetWidth;
+ }
+ if (top + menu.offsetHeight > window.innerHeight) {
+ top = window.innerHeight - menu.offsetHeight;
+ }
+
+ menu.style.left = `${left}px`;
+ menu.style.top = `${top}px`;
}
});
diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts
index c3f43c3..8aca504 100755
--- a/frontend/wailsjs/go/main/App.d.ts
+++ b/frontend/wailsjs/go/main/App.d.ts
@@ -27,10 +27,12 @@ export function PromptUploadDirectory(arg1:string,arg2:string):Promise
;
export function PromptUploadFiles(arg1:string,arg2:string):Promise;
-export function QueueTransfer(arg1:string,arg2:string,arg3:string,arg4:string,arg5:string,arg6:string,arg7:number):Promise;
+export function QueueTransfer(arg1:string,arg2:string,arg3:string,arg4:string,arg5:string,arg6:string,arg7:number,arg8:boolean,arg9:number):Promise;
export function Rename(arg1:string,arg2:string,arg3:string):Promise;
+export function ReorderConnections(arg1:Array):Promise;
+
export function SaveConnection(arg1:config.ConnectionConfig,arg2:string):Promise;
-export function TransferItems(arg1:string,arg2:string,arg3:string,arg4:Array):Promise;
+export function TransferItems(arg1:string,arg2:string,arg3:string,arg4:Array,arg5:boolean,arg6:number):Promise;
diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js
index 1e0950e..9b0e0d1 100755
--- a/frontend/wailsjs/go/main/App.js
+++ b/frontend/wailsjs/go/main/App.js
@@ -46,18 +46,22 @@ export function PromptUploadFiles(arg1, arg2) {
return window['go']['main']['App']['PromptUploadFiles'](arg1, arg2);
}
-export function QueueTransfer(arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
- return window['go']['main']['App']['QueueTransfer'](arg1, arg2, arg3, arg4, arg5, arg6, arg7);
+export function QueueTransfer(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) {
+ return window['go']['main']['App']['QueueTransfer'](arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
}
export function Rename(arg1, arg2, arg3) {
return window['go']['main']['App']['Rename'](arg1, arg2, arg3);
}
+export function ReorderConnections(arg1) {
+ return window['go']['main']['App']['ReorderConnections'](arg1);
+}
+
export function SaveConnection(arg1, arg2) {
return window['go']['main']['App']['SaveConnection'](arg1, arg2);
}
-export function TransferItems(arg1, arg2, arg3, arg4) {
- return window['go']['main']['App']['TransferItems'](arg1, arg2, arg3, arg4);
+export function TransferItems(arg1, arg2, arg3, arg4, arg5, arg6) {
+ return window['go']['main']['App']['TransferItems'](arg1, arg2, arg3, arg4, arg5, arg6);
}
diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts
index c32dbbd..61fed85 100755
--- a/frontend/wailsjs/go/models.ts
+++ b/frontend/wailsjs/go/models.ts
@@ -9,6 +9,7 @@ export namespace config {
bucket?: string;
region?: string;
path_style?: boolean;
+ secure?: boolean;
username?: string;
keychain_key?: string;
@@ -26,6 +27,7 @@ export namespace config {
this.bucket = source["bucket"];
this.region = source["region"];
this.path_style = source["path_style"];
+ this.secure = source["secure"];
this.username = source["username"];
this.keychain_key = source["keychain_key"];
}
@@ -96,6 +98,8 @@ export namespace transfer {
eta_seconds: number;
status: string;
error?: string;
+ verify: boolean;
+ limit_mbps: number;
static createFrom(source: any = {}) {
return new Transfer(source);
@@ -113,6 +117,8 @@ export namespace transfer {
this.eta_seconds = source["eta_seconds"];
this.status = source["status"];
this.error = source["error"];
+ this.verify = source["verify"];
+ this.limit_mbps = source["limit_mbps"];
}
}
diff --git a/frontend/wailsjs/runtime/package.json b/frontend/wailsjs/runtime/package.json
old mode 100644
new mode 100755
diff --git a/frontend/wailsjs/runtime/runtime.d.ts b/frontend/wailsjs/runtime/runtime.d.ts
old mode 100644
new mode 100755
diff --git a/frontend/wailsjs/runtime/runtime.js b/frontend/wailsjs/runtime/runtime.js
old mode 100644
new mode 100755
diff --git a/internal/protocols/sftp/sftp.go b/internal/protocols/sftp/sftp.go
index 4e37775..df1eadc 100644
--- a/internal/protocols/sftp/sftp.go
+++ b/internal/protocols/sftp/sftp.go
@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"path/filepath"
+ "strings"
"time"
"github.com/pkg/sftp"
@@ -32,8 +33,16 @@ func (e *SFTPExplorer) Connect() error {
}
var authMethod ssh.AuthMethod
- signer, err := ssh.ParsePrivateKey([]byte(e.secret))
- if err == nil {
+
+ secretStr := strings.TrimSpace(e.secret)
+ // Handle case where newlines might be escaped by mistake
+ secretStr = strings.ReplaceAll(secretStr, "\\n", "\n")
+
+ if strings.Contains(secretStr, "-----BEGIN") {
+ signer, err := ssh.ParsePrivateKey([]byte(secretStr))
+ if err != nil {
+ return fmt.Errorf("failed to parse private key: %w", err)
+ }
authMethod = ssh.PublicKeys(signer)
} else {
authMethod = ssh.Password(e.secret)