import { state, notify } from "../store.js"; import { t } from "../i18n.js"; import { iconMarkup } from "../icons.js"; import { escapeHtml } from "../dom.js"; import { connectFromSite, PROTOCOL_LABELS } from "../connectActions.js"; import * as ipc from "../ipc.js"; const PROTOCOLS = ["sftp", "ftp", "ftps"]; const DEFAULT_PORTS = { sftp: 22, ftp: 21, ftps: 990 }; let overlayEl = null; let viewMode = "list"; let form = emptyForm(); let editingId = null; let saving = false; let deleteConfirmId = null; function emptyForm() { return { name: "", protocol: "sftp", host: "", port: "", username: "", password: "", keyPath: "", folder: "", note: "", }; } export function openSiteManager(siteId) { const site = state.sites.find((s) => s.id === siteId); if (!site) return; editingId = siteId; form = { name: site.name, protocol: site.protocol, host: site.host, port: String(site.port), username: site.username, password: site.password ?? "", keyPath: site.keyPath ?? "", folder: site.folder ?? "", note: site.note ?? "", }; viewMode = "form"; saving = false; deleteConfirmId = null; state.showSettingsDialog = false; state.showSiteManager = true; notify(); } export function openSiteManagerNew() { editingId = null; form = emptyForm(); viewMode = "list"; saving = false; deleteConfirmId = null; state.showSiteManager = true; notify(); } function close() { state.showSiteManager = false; overlayEl?.remove(); overlayEl = null; notify(); } function switchToList() { viewMode = "list"; deleteConfirmId = null; notify(); } async function saveCurrent() { if (saving) return; const name = form.name.trim(); const host = form.host.trim(); const username = form.username.trim(); if (!name || !host || !username) return; let port = DEFAULT_PORTS[form.protocol]; if (form.port.trim()) { const parsed = Number(form.port); if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) return; port = parsed; } saving = true; notify(); const site = { id: editingId || crypto.randomUUID(), name, protocol: form.protocol, host, port, username, password: form.password || null, keyPath: form.keyPath || null, folder: form.folder || null, note: form.note || null, }; try { await ipc.saveSite(site); state.sites = await ipc.listSites(); state.statusMessage = t("siteManager.saved"); viewMode = "list"; saving = false; notify(); } catch { saving = false; notify(); } } async function deleteSiteAction(id) { const site = state.sites.find((s) => s.id === id); await ipc.deleteSite(id); await ipc.deletePassword(id); state.sites = state.sites.filter((s) => s.id !== id); if (site) state.statusMessage = `Removed ${site.name}`; notify(); } async function browseKeyFile() { try { const dialog = window.__TAURI__.dialog; const path = await dialog.open({ title: "Choose SSH key", multiple: false, directory: false }); if (typeof path === "string") { form.keyPath = path; const input = overlayEl?.querySelector("[data-field=keyPath]"); if (input) input.value = path; } } catch { /* dialog plugin unavailable / user cancelled */ } } function build() { const overlay = document.createElement("div"); overlay.className = "overlay"; overlay.addEventListener("mousedown", (e) => { if (e.target === overlay) close(); }); const dialog = document.createElement("div"); dialog.className = "dialog"; dialog.style.width = "440px"; dialog.addEventListener("mousedown", (e) => e.stopPropagation()); dialog.addEventListener("keydown", (e) => { if (e.key === "Enter" && viewMode === "form") saveCurrent(); }); dialog.innerHTML = `
${iconMarkup("serverSquare", 16)} ${t("siteManager.title")}
`; dialog.querySelector('[data-action="close"]').addEventListener("click", close); overlay.appendChild(dialog); return overlay; } function renderList() { const title = overlayEl.querySelector('[data-role="title"]'); title.textContent = t("siteManager.title"); const body = overlayEl.querySelector('[data-role="body"]'); let html = ""; if (!state.sites.length) { html += `
${t("siteManager.noSites")}
`; } else { html += state.sites.map( (s) => `
${PROTOCOL_LABELS[s.protocol] ?? s.protocol} ${escapeHtml(s.name)}
${escapeHtml(s.username)}@${escapeHtml(s.host)}:${s.port}
`, ).join(""); } html += `
`; body.innerHTML = html; body.querySelectorAll("[data-action='sm-connect']").forEach((btn) => { btn.addEventListener("click", async () => { const site = state.sites.find((s) => s.id === btn.dataset.id); if (site) { close(); await connectFromSite(site); } }); }); body.querySelectorAll("[data-action='sm-edit']").forEach((btn) => { btn.addEventListener("click", () => openSiteManager(btn.dataset.id)); }); body.querySelectorAll("[data-action='sm-delete']").forEach((btn) => { btn.addEventListener("click", async () => { await deleteSiteAction(btn.dataset.id); if (overlayEl) renderList(); }); }); body.querySelector("[data-action='sm-add']").addEventListener("click", () => { editingId = null; form = emptyForm(); viewMode = "form"; notify(); }); } function renderForm() { const title = overlayEl.querySelector('[data-role="title"]'); title.textContent = editingId ? t("siteManager.editSite") : t("siteManager.addSite"); const body = overlayEl.querySelector('[data-role="body"]'); body.innerHTML = `
${t("siteManager.name")}
${t("siteManager.protocol")}
${PROTOCOLS.map((p) => ``).join("")}
${t("connect.host")}
${t("connect.username")}
${t("connect.port")}
${t("connect.password")}

${t("siteManager.keyHint")}

${t("connect.keyFile")}
${t("siteManager.folder")}
${t("siteManager.note")}
`; body.querySelector('[data-action="cancel"]').addEventListener("click", switchToList); body.querySelector('[data-action="save"]').addEventListener("click", saveCurrent); body.querySelectorAll("[data-field]").forEach((input) => { input.addEventListener("input", () => { form[input.dataset.field] = input.value; }); }); const passInput = body.querySelector('[data-field="password"]'); const toggleBtn = body.querySelector('[data-action="toggle-pass"]'); if (toggleBtn && passInput) { toggleBtn.addEventListener("click", () => { const showing = passInput.type === "text"; passInput.type = showing ? "password" : "text"; toggleBtn.innerHTML = iconMarkup(showing ? "eye" : "eyeClosed", 15); }); } const browseBtn = body.querySelector('[data-action="browse"]'); if (browseBtn) browseBtn.addEventListener("click", browseKeyFile); body.querySelectorAll("[data-protocol]").forEach((btn) => { btn.addEventListener("click", () => { const proto = btn.dataset.protocol; form.protocol = proto; const portInput = body.querySelector('[data-field="port"]'); if (!form.port) { portInput.placeholder = String(DEFAULT_PORTS[proto]); } body.querySelectorAll("[data-protocol]").forEach((b) => b.classList.toggle("active", b === btn)); const keySection = body.querySelector('[data-role="key-section"]'); if (keySection) keySection.style.display = proto === "sftp" ? "block" : "none"; }); }); setTimeout(() => body.querySelector('[data-field="name"]')?.focus(), 0); } export function renderSiteManager() { if (!state.showSiteManager) { if (overlayEl) { overlayEl.remove(); overlayEl = null; } return; } if (!overlayEl) { overlayEl = build(); document.body.appendChild(overlayEl); } if (viewMode === "list") { renderList(); } else { renderForm(); } }