(() => { // Глобальные обработчики ошибок в renderer window.addEventListener('error', (event) => { console.error('Global error in renderer:', event.error); }); window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled promise rejection in renderer:', event.reason); }); let apiBase = window.config?.apiBase || "http://localhost:8080/api/v1"; const qs = (sel) => document.querySelector(sel); const qsi = (sel) => document.querySelector(sel); const qst = (sel) => document.querySelector(sel); const yaml = window.jsyaml; function setMode(mode) { ["inline", "yaml", "form"].forEach((m) => { const el = qs(`#${m}-section`); if (el) el.classList.toggle("hidden", m !== mode); const encryptDecryptRow = qs("#encrypt-decrypt-row"); if (encryptDecryptRow) { encryptDecryptRow.classList.toggle("hidden", mode !== "yaml"); } }); } function basicAuthHeader(password) { const token = btoa(`knocker:${password}`); return { Authorization: `Basic ${token}` }; } function updateStatus(msg) { const el = qs("#status"); if (el) { el.textContent = msg; setTimeout(() => { el.textContent = ""; }, 5000); // Очищаем через 5 секунд } } const targets = [{ protocol: "tcp", host: "127.0.0.1", port: 22, gateway: "" }]; function renderTargets() { const list = qs("#targetsList"); if (!list) return; list.innerHTML = ""; targets.forEach((t, idx) => { const row = document.createElement("div"); row.className = "target-row"; row.innerHTML = ` `; list.appendChild(row); }); } function serializeFormTargetsToInline() { return targets .map( (t) => `${t.protocol}:${t.host}:${t.port}${t.gateway ? `:${t.gateway}` : ""}` ) .join(";"); } function convertInlineToYaml(targetsStr, delay, waitConnection) { const entries = (targetsStr || "").split(";").filter(Boolean); const config = { targets: entries.map((e) => { const parts = e.split(":"); const protocol = parts[0] || "tcp"; const host = parts[1] || "127.0.0.1"; const port = parseInt(parts[2] || "22", 10); return { protocol, host, ports: [port], wait_connection: !!waitConnection, }; }), delay: delay || "1s", }; return yaml.dump(config, { lineWidth: 120 }); } function convertYamlToInline(yamlText) { if (!yamlText.trim()) return { targets: "tcp:127.0.0.1:22", delay: "1s", waitConnection: false, }; const config = yaml.load(yamlText) || {}; const list = []; (config.targets || []).forEach((t) => { const protocol = t.protocol || "tcp"; const host = t.host || "127.0.0.1"; const ports = t.ports || [t.port] || [22]; (Array.isArray(ports) ? ports : [ports]).forEach((p) => list.push(`${protocol}:${host}:${p}`) ); }); return { targets: list.join(";"), delay: config.delay || "1s", waitConnection: !!config.targets?.[0]?.wait_connection, }; } function extractPathFromYaml(text) { try { const doc = yaml.load(text); if (doc && typeof doc === "object" && typeof doc.path === "string") return doc.path; } catch { } return ""; } function patchYamlPath(text, newPath) { try { const doc = text.trim() ? yaml.load(text) : {}; if (doc && typeof doc === "object") { doc.path = newPath || ""; return yaml.dump(doc, { lineWidth: 120 }); } } catch { } return text; } function isEncryptedYaml(text) { return (text || "").trim().startsWith("ENCRYPTED:"); } // Функция для обновления конфига из настроек function updateConfigFromSettings() { window.api.getConfig('apiBase') .then((saved) => { if (typeof saved === 'string' && saved.trim()) { apiBase = saved; if (qsi('#apiUrl')) { qsi('#apiUrl').value = apiBase; } } }) .catch(() => { }); window.api.getConfig('gateway') .then((saved) => { if (qsi('#gateway')) { qsi('#gateway').value = saved || ''; } }) .catch(() => { }); window.api.getConfig('inlineTargets') .then((saved) => { if (qsi('#targets')) { qsi('#targets').value = saved || ''; } }) .catch(() => { }); window.api.getConfig('delay') .then((saved) => { if (qsi('#delay')) { qsi('#delay').value = saved || ''; } }) .catch(() => { }); } // событие возникающее когда загружается страница основная приложения window.addEventListener("DOMContentLoaded", () => { document.querySelectorAll('input[name="mode"]').forEach((r) => { r.addEventListener("change", (e) => setMode(e?.target?.value || '')); }); // Инициализация/восстановление apiBase из конфига window.api.getConfig('apiBase') .then((saved) => { if (typeof saved === 'string' && saved.trim()) { apiBase = saved; } }) .catch(() => { }) .finally(() => { if (qsi('#apiUrl')) { qsi('#apiUrl').value = apiBase; } }); // Сохранение apiBase при изменении поля qsi('#apiUrl')?.addEventListener('change', async () => { const val = qsi('#apiUrl').value.trim(); if (!val) return; apiBase = val; try { await window.api.setConfig('apiBase', val); } catch { } updateStatus('API URL сохранён'); }); // Инициализация/восстановление gateway из конфига window.api.getConfig('gateway') .then((saved) => { if (qsi('#gateway')) { qsi('#gateway').value = saved || ''; } }) .catch(() => { }); // Сохранение Gateway при изменении поля qsi('#gateway')?.addEventListener('change', async () => { const val = qsi('#gateway').value.trim(); try { await window.api.setConfig('gateway', val); } catch { } updateStatus('Gateway сохранён'); }); // Инициализация/восстановление inlineTargets из конфига window.api.getConfig('inlineTargets') .then((saved) => { if (qsi('#targets')) { qsi('#targets').value = saved || ''; } }) .catch(() => { }); // Сохранение inlineTargets при изменении поля qsi('#targets')?.addEventListener('change', async () => { const val = qsi('#targets').value.trim(); try { await window.api.setConfig('inlineTargets', val); } catch { } updateStatus('inlineTargets сохранёны'); }); // Инициализация/восстановление delay из конфига window.api.getConfig('delay') .then((saved) => { if (qsi('#delay')) { qsi('#delay').value = saved || ''; } }) .catch(() => { }); // Сохранение delay при изменении поля qsi('#delay')?.addEventListener('change', async () => { const val = qsi('#delay').value.trim(); try { await window.api.setConfig('delay', val); } catch { } updateStatus('Задержка сохранёна'); }); qsi("#addTarget")?.addEventListener("click", () => { targets.push({ protocol: "tcp", host: "127.0.0.1", port: 22 }); renderTargets(); }); qs("#targetsList")?.addEventListener("input", (e) => { const row = e.target.closest(".target-row"); if (!row) return; const idx = Array.from(row.parentElement.children).indexOf(row); const key = e.target.getAttribute("data-k"); if (idx >= 0 && key) { const val = e.target.type === "number" ? Number(e.target.value) : e.target.value; targets[idx][key] = val; } }); qs("#targetsList")?.addEventListener("click", (e) => { if (!e.target.classList.contains("remove")) { return; } const row = e.target.closest(".target-row"); const idx = Array.from(row.parentElement.children).indexOf(row); if (idx >= 0) { targets.splice(idx, 1); renderTargets(); } }); qs("#openFile")?.addEventListener("click", async () => { const res = await window.api.openFile(); if (!(!res.canceled && res.content !== undefined)) { return; } qst("#configYAML").value = res.content; const p = extractPathFromYaml(res.content); qsi("#serverFilePath").value = p || ""; updateStatus(`Открыт файл: ${res.filePath}`); }); qs("#saveFile")?.addEventListener("click", async () => { const content = qst("#configYAML").value; const suggested = content.trim().startsWith("ENCRYPTED:") ? "config.encrypted" : "config.yaml"; const res = await window.api.saveAs({ suggestedName: suggested, content, }); if (!res.canceled && res.filePath) { updateStatus(`Сохранено: ${res.filePath}`); await window.api.revealInFolder(res.filePath); } }); qsi("#serverFilePath")?.addEventListener("input", () => { const newPath = qsi("#serverFilePath").value; const current = qst("#configYAML").value; qst("#configYAML").value = patchYamlPath(current, newPath); }); qs("#execute")?.addEventListener("click", async () => { updateStatus("Выполнение…"); const password = qsi("#password").value; const mode = document.querySelector('input[name="mode"]:checked')?.value || ''; // Проверяем, нужно ли использовать локальное простукивание const useLocalKnock = !apiBase || apiBase.trim() === '' || apiBase === 'internal'; if (useLocalKnock) { // Локальное простукивание через Node.js try { let targets = []; let delay = qsi("#delay").value || '1s'; const verbose = qsi("#verbose").checked; if (mode === "inline") { targets = qsi("#targets").value.split(';').filter(t => t.trim()); } else if (mode === "form") { targets = [serializeFormTargetsToInline()]; } else if (mode === "yaml") { // Для YAML режима извлекаем targets из YAML const yamlContent = qst("#configYAML").value; try { const config = yaml.load(yamlContent); if (config?.targets && Array.isArray(config.targets)) { targets = config.targets.map(t => { const protocol = t.protocol || 'tcp'; const host = t.host || '127.0.0.1'; const ports = t.ports || [t.port] || [22]; return ports.map(port => `${protocol}:${host}:${port}`); }).flat(); delay = config.delay || delay; } } catch (e) { updateStatus(`Ошибка парсинга YAML: ${e.message}`); return; } } if (targets.length === 0) { updateStatus("Нет целей для простукивания"); return; } // Получаем gateway из конфигурации или поля const gateway = qsi('#gateway')?.value?.trim() || ''; const result = await window.api.localKnock({ targets, delay, verbose, gateway }); if (result?.success) { const summary = result.summary; updateStatus(`Локальное простукивание завершено: ${summary.successful}/${summary.total} успешно`); // Логируем детальные результаты в консоль if (verbose) { console.log('Local knock results:', result.results); } } else { const errorMsg = result?.error || 'Неизвестная ошибка локального простукивания'; updateStatus(`Ошибка локального простукивания: ${errorMsg}`); console.error('Local knock failed:', result); } } catch (e) { updateStatus(`Ошибка: ${e?.message || String(e)}`); } return; } // API простукивание через HTTP const body = {}; if (mode === "yaml") { body.config_yaml = qst("#configYAML").value; } else if (mode === "inline") { body.targets = qsi("#targets").value; body.delay = qsi("#delay").value; body.verbose = qsi("#verbose").checked; body.waitConnection = qsi("#waitConnection").checked; body.gateway = qsi("#gateway").value; } else { body.targets = serializeFormTargetsToInline(); body.delay = qsi("#delay").value; body.verbose = qsi("#verbose").checked; body.waitConnection = qsi("#waitConnection").checked; } let result; try { result = await fetch(`${apiBase}/knock-actions/execute`, { method: "POST", headers: { "Content-Type": "application/json", ...basicAuthHeader(password), }, body: JSON.stringify(body), }); if (result?.ok) { updateStatus("Успешно простучали через API..."); } else { updateStatus(`Ошибка API: ${result.statusText}`); } } catch (e) { updateStatus(`Ошибка: ${e?.message || String(e)}`); } }); qs("#encrypt")?.addEventListener("click", async () => { const password = qsi("#password").value; const content = qst("#configYAML").value; const pathFromYaml = extractPathFromYaml(content); if (!content.trim()) return; const url = `${apiBase}/knock-actions/encrypt`; const payload = { yaml: content }; try { const r = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", ...basicAuthHeader(password), }, body: JSON.stringify(payload), }); const res = await r.json(); const encrypted = res?.encrypted || ""; qst("#configYAML").value = encrypted; updateStatus("Зашифровано"); if (!pathFromYaml) { await window.api.saveAs({ suggestedName: "config.encrypted", content: encrypted, }); } } catch (e) { updateStatus(`Ошибка: ${e?.message || String(e)}`); } }); qs("#decrypt")?.addEventListener("click", async () => { const password = qsi("#password").value; const content = qst("#configYAML").value; if (!content.trim() || !isEncryptedYaml(content)) return; try { const r = await fetch(`${apiBase}/knock-actions/decrypt`, { method: "POST", headers: { "Content-Type": "application/json", ...basicAuthHeader(password), }, body: JSON.stringify({ encrypted: content }), }); const res = await r.json(); const plain = res?.yaml || ""; qst("#configYAML").value = plain; const p = extractPathFromYaml(plain); if (p) qsi("#serverFilePath").value = p; updateStatus("Расшифровано"); } catch (e) { updateStatus(`Ошибка: ${e?.message || String(e)}`); } }); renderTargets(); // Обновляем конфиг при фокусе окна (если настройки были изменены) window.addEventListener('focus', updateConfigFromSettings); // Диагностические функции window.testNetworkInterfaces = async () => { try { const result = await window.api.getNetworkInterfaces(); if (result.success) { console.log('Network interfaces:', result.interfaces); updateStatus('Network interfaces logged to console'); } else { updateStatus(`Error getting interfaces: ${result.error}`); } } catch (e) { updateStatus(`Error: ${e.message}`); } }; window.testConnection = async () => { try { const gateway = qsi('#gateway')?.value?.trim(); if (!gateway) { updateStatus('Please set gateway first'); return; } const result = await window.api.testConnection({ host: '192.168.89.1', port: 2655, localAddress: gateway }); if (result.success) { updateStatus(`Test connection successful: ${result.message}`); console.log('Test connection result:', result); } else { updateStatus(`Test connection failed: ${result.error}`); } } catch (e) { updateStatus(`Error: ${e.message}`); } }; // Добавляем диагностические кнопки в консоль console.log('Diagnostic functions available:'); console.log('- window.testNetworkInterfaces() - Show network interfaces'); console.log('- window.testConnection() - Test connection with gateway'); }); })();