Rust SFTP service: russh-sftp client on same SSH connection, DashMap storage, list/read/write/mkdir/delete/rename/stat ops. 5MB file size guard. Non-fatal SFTP failure (terminal still works). Vue frontend: FileTree with all toolbar buttons wired (upload, download, delete, mkdir, refresh), TransferProgress panel, useSftp composable with CWD following via Tauri events. MainLayout wired with SFTP sidebar. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
121 lines
3.1 KiB
Rust
121 lines
3.1 KiB
Rust
//! Tauri commands for SSH session management.
|
|
//!
|
|
//! All commands are async because russh operations are inherently asynchronous.
|
|
//! The SSH service is accessed via `State<AppState>` and the `AppHandle` is
|
|
//! used for event emission.
|
|
|
|
use tauri::{AppHandle, State};
|
|
|
|
use crate::ssh::session::{AuthMethod, SessionInfo};
|
|
use crate::AppState;
|
|
|
|
/// Connect to an SSH server with password authentication.
|
|
///
|
|
/// Opens a PTY, starts a shell, and begins streaming output via
|
|
/// `ssh:data:{session_id}` events. Also opens an SFTP subsystem channel on
|
|
/// the same connection. Returns the session UUID.
|
|
#[tauri::command]
|
|
pub async fn connect_ssh(
|
|
hostname: String,
|
|
port: u16,
|
|
username: String,
|
|
password: String,
|
|
cols: u32,
|
|
rows: u32,
|
|
app_handle: AppHandle,
|
|
state: State<'_, AppState>,
|
|
) -> Result<String, String> {
|
|
state
|
|
.ssh
|
|
.connect(
|
|
app_handle,
|
|
&hostname,
|
|
port,
|
|
&username,
|
|
AuthMethod::Password(password),
|
|
cols,
|
|
rows,
|
|
&state.sftp,
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Connect to an SSH server with private key authentication.
|
|
///
|
|
/// The `private_key_pem` should be the PEM-encoded private key content.
|
|
/// `passphrase` is `None` if the key is not encrypted.
|
|
///
|
|
/// Opens a PTY, starts a shell, and begins streaming output via
|
|
/// `ssh:data:{session_id}` events. Returns the session UUID.
|
|
#[tauri::command]
|
|
pub async fn connect_ssh_with_key(
|
|
hostname: String,
|
|
port: u16,
|
|
username: String,
|
|
private_key_pem: String,
|
|
passphrase: Option<String>,
|
|
cols: u32,
|
|
rows: u32,
|
|
app_handle: AppHandle,
|
|
state: State<'_, AppState>,
|
|
) -> Result<String, String> {
|
|
state
|
|
.ssh
|
|
.connect(
|
|
app_handle,
|
|
&hostname,
|
|
port,
|
|
&username,
|
|
AuthMethod::Key {
|
|
private_key_pem,
|
|
passphrase,
|
|
},
|
|
cols,
|
|
rows,
|
|
&state.sftp,
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Write data to a session's PTY stdin.
|
|
///
|
|
/// The `data` parameter is a string that will be sent as UTF-8 bytes.
|
|
#[tauri::command]
|
|
pub async fn ssh_write(
|
|
session_id: String,
|
|
data: String,
|
|
state: State<'_, AppState>,
|
|
) -> Result<(), String> {
|
|
state.ssh.write(&session_id, data.as_bytes()).await
|
|
}
|
|
|
|
/// Resize the PTY window for a session.
|
|
#[tauri::command]
|
|
pub async fn ssh_resize(
|
|
session_id: String,
|
|
cols: u32,
|
|
rows: u32,
|
|
state: State<'_, AppState>,
|
|
) -> Result<(), String> {
|
|
state.ssh.resize(&session_id, cols, rows).await
|
|
}
|
|
|
|
/// Disconnect an SSH session — closes the channel and removes it.
|
|
///
|
|
/// Also removes the associated SFTP client.
|
|
#[tauri::command]
|
|
pub async fn disconnect_ssh(
|
|
session_id: String,
|
|
state: State<'_, AppState>,
|
|
) -> Result<(), String> {
|
|
state.ssh.disconnect(&session_id, &state.sftp).await
|
|
}
|
|
|
|
/// List all active SSH sessions (metadata only).
|
|
#[tauri::command]
|
|
pub async fn list_ssh_sessions(
|
|
state: State<'_, AppState>,
|
|
) -> Result<Vec<SessionInfo>, String> {
|
|
Ok(state.ssh.list_sessions())
|
|
}
|