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 = `
`;
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}
${iconMarkup("playCircle", 14)}
${iconMarkup("pen", 13)}
${iconMarkup("trash", 14)}
`,
).join("");
}
html += `
${iconMarkup("addCircle", 14)}${t("siteManager.addSite")}
`;
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) => `${PROTOCOL_LABELS[p]} `).join("")}
${t("connect.host")}
${t("siteManager.keyHint")}
${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();
}
}