Wherry/frontend/js/connectActions.js

162 lines
5 KiB
JavaScript

// Connection flow shared by the New Connection dialog, the welcome screen's
// recent-connections list and the toolbar History popover. Mirrors
// src/ui/dialogs/connection/{actions,history}.rs.
import { state, notify } from "./store.js";
import * as ipc from "./ipc.js";
export const DEFAULT_PORTS = { sftp: 22, ftp: 21, ftps: 990 };
export const PROTOCOL_LABELS = { sftp: "SFTP", ftp: "FTP", ftps: "FTPS" };
function uuid() {
return crypto.randomUUID();
}
async function refreshHistory() {
try {
state.history = await ipc.listHistory();
} catch {
/* history is best-effort UI sugar, ignore failures */
}
}
/** Runs a connect attempt for already-built ConnectionParams; used by the
* dialog's Connect button and by history reconnect/edit. */
export async function spawnConnect(params) {
state.connectError = "";
state.connectLoading = true;
notify();
try {
const connectionId = await ipc.connect(params);
const entries = await ipc.remoteList(connectionId, "/");
// The backend reuses the same connectionId for repeat connections to the
// same host/port/username (see canonical_id in src/commands.rs::connect),
// so reconnecting to an already-open server must update its existing tab
// rather than push a second one with the same id.
const existing = state.tabs.find((t) => t.id === connectionId);
if (existing) {
existing.label = params.label;
existing.params = params;
existing.status = "connected";
existing.remotePath = "/";
existing.remoteEntries = entries;
existing.remoteSelected = [];
existing.remoteSelectAnchor = null;
existing.loading = false;
} else {
state.tabs.push({
id: connectionId,
label: params.label,
params,
status: "connected",
remotePath: "/",
remoteEntries: entries,
remoteSelected: [],
remoteSelectAnchor: null,
loading: false,
sort: { col: "name", dir: "asc" },
});
}
state.activeTabId = connectionId;
state.lastRemoteTabId = connectionId;
state.activePane = "remote";
// Open dual-pane: local (left) + remote (right)
if (state.layout.panes.length === 1 && state.layout.panes[0].tabId === null) {
state.layout.panes.push({ id: `pane-${Date.now()}`, tabId: connectionId, flex: 1 });
state.layout.panes[0].flex = 1;
} else {
state.layout.panes[0].tabId = connectionId;
}
state.connectLoading = false;
state.showConnectDialog = false;
state.statusMessage = `Connected to ${params.host}`;
notify();
await refreshHistory();
notify();
return { ok: true };
} catch (err) {
state.connectLoading = false;
state.connectError = String(err);
state.statusMessage = `Connection failed: ${err}`;
notify();
return { ok: false, error: String(err) };
}
}
/** Validates raw dialog form fields and turns them into ConnectionParams. */
export function buildParamsFromForm(form) {
const host = form.host.trim();
const username = form.username.trim();
const portStr = form.port.trim();
if (!host) return { ok: false, error: "connect.errorHost" };
if (!username) return { ok: false, error: "connect.errorUser" };
let port = DEFAULT_PORTS[form.protocol];
if (portStr) {
const parsed = Number(portStr);
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
return { ok: false, error: "connect.errorPort" };
}
port = parsed;
}
const protocolLabel = PROTOCOL_LABELS[form.protocol];
const params = {
id: uuid(),
label: form.label.trim() || `${host} (${protocolLabel})`,
protocol: form.protocol,
host,
port,
username,
password: form.password ? form.password : null,
keyPath: form.keyPath ? form.keyPath : null,
};
return { ok: true, params };
}
/** Click on a history row — reconnect straight away, no dialog. */
export function reconnectFromHistory(entry) {
const params = {
id: entry.connId,
label: `${entry.username}@${entry.host}`,
protocol: entry.protocol,
host: entry.host,
port: entry.port,
username: entry.username,
password: null, // history doesn't store passwords
keyPath: entry.keyPath ?? null,
};
return spawnConnect(params);
}
/** Click on a saved-site row (welcome screen). */
export function connectFromSite(site) {
const params = {
id: site.id,
label: site.name,
protocol: site.protocol,
host: site.host,
port: site.port,
username: site.username,
password: site.password ?? null,
keyPath: site.keyPath ?? null,
};
return spawnConnect(params);
}
/** "Save" on a history row — turns a one-off connection into a permanent Site. */
export async function saveHistoryAsSite(entry) {
const site = {
id: entry.connId,
name: `${entry.username}@${entry.host}`,
protocol: entry.protocol,
host: entry.host,
port: entry.port,
username: entry.username,
keyPath: entry.keyPath ?? null,
folder: null,
note: null,
};
await ipc.saveSite(site);
state.statusMessage = `Saved ${site.name} as a site`;
notify();
}