/* functions.js — CRUD операции с функциями */ // setCodeMode переключает режим формы между вводом кода и загрузкой архива. // prefix: 'c' (create) или 'e' (edit) // mode: 'code' | 'archive' function setCodeMode(prefix, mode) { var codeArea = document.getElementById(prefix + '-code-area'); var archiveArea = document.getElementById(prefix + '-archive-area'); var btnCode = document.getElementById(prefix + '-mode-code'); var btnArchive = document.getElementById(prefix + '-mode-archive'); if (!codeArea || !archiveArea) return; var isCode = mode === 'code'; codeArea.style.display = isCode ? '' : 'none'; archiveArea.style.display = isCode ? 'none' : ''; if (btnCode) btnCode.style.opacity = isCode ? '1' : '0.5'; if (btnArchive) btnArchive.style.opacity = isCode ? '0.5' : '1'; } // lintArchiveFile — отправляет zip-архив на /console/api/ai/lint-archive, // показывает результат пофайлово в блоке {prefix}-ai-result рядом с кнопкой. function lintArchiveFile(prefix) { var input = document.getElementById(prefix + '-archive-file'); var resEl = document.getElementById(prefix + '-ai-result'); var btn = document.getElementById(prefix + '-lint-btn'); function showRes(text, bg, color) { if (resEl) { resEl.style.display = 'block'; resEl.style.background = bg; resEl.style.color = color; resEl.textContent = text; } } if (!input || !input.files || !input.files[0]) { showRes('Выберите .zip файл', '#3a1a1a', '#f88'); return; } var file = input.files[0]; if (file.size > 100 * 1024) { showRes('Архив слишком большой: максимум 100 KB', '#3a1a1a', '#f88'); return; } if (btn) { btn.disabled = true; btn.textContent = '⏳ Проверяю…'; } showRes('Запускаю линтер…', 'var(--bg-alt)', 'var(--fg)'); var fd = new FormData(); fd.append('archive', file); fetch(API_BASE + '/ai/lint-archive', { method: 'POST', headers: authHeaders(), body: fd }) .then(function(r) { return r.json(); }) .then(function(data) { if (btn) { btn.disabled = false; btn.textContent = '🔍 Проверка архива линтером'; } if (data.error) { showRes('Ошибка: ' + data.error, '#3a1a1a', '#f88'); return; } var results = data.results || []; if (results.length === 0) { showRes('Нет поддерживаемых файлов в архиве (.py, .js, .rb, .php)', '#3a2a00', '#ffa'); return; } var ok = results.filter(function(r) { return r.ok; }).length; var fail = results.filter(function(r) { return !r.ok; }).length; var lines = results.map(function(r) { return (r.ok ? '✅ ' : '❌ ') + r.file + (r.output ? '\n ' + r.output : ''); }); var summary = ok + ' OK, ' + fail + ' ошибок\n\n' + lines.join('\n'); showRes(summary, fail > 0 ? '#3a1a1a' : '#1a3a1a', fail > 0 ? '#f88' : '#8f8'); }) .catch(function(e) { if (btn) { btn.disabled = false; btn.textContent = '🔍 Проверка архива линтером'; } showRes('Ошибка сети: ' + e.message, '#3a2a00', '#ffa'); }); } // explainArchiveFile — отправляет zip-архив на /console/api/ai/explain-archive, // показывает ответ LLM в блоке {prefix}-ai-result. function explainArchiveFile(prefix) { var input = document.getElementById(prefix + '-archive-file'); var resEl = document.getElementById(prefix + '-ai-result'); var btn = document.getElementById(prefix + '-explain-archive-btn'); function showRes(text, bg, color) { if (resEl) { resEl.style.display = 'block'; resEl.style.background = bg; resEl.style.color = color; resEl.textContent = text; } } if (!input || !input.files || !input.files[0]) { showRes('Выберите .zip файл', '#3a1a1a', '#f88'); return; } var file = input.files[0]; if (file.size > 100 * 1024) { showRes('Архив слишком большой: максимум 100 KB', '#3a1a1a', '#f88'); return; } if (btn) { btn.disabled = true; btn.textContent = '⏳ Спрашиваю LLM…'; } showRes('Запрашиваю у LLM…', 'var(--bg-alt)', 'var(--fg)'); var fd = new FormData(); fd.append('archive', file); fetch(API_BASE + '/ai/explain-archive', { method: 'POST', headers: authHeaders(), body: fd }) .then(function(r) { return r.json(); }) .then(function(data) { if (btn) { btn.disabled = false; btn.textContent = '📖 LLM: Что делает?'; } if (data.error) { showRes('Ошибка: ' + data.error, '#3a2a00', '#ffa'); return; } showRes(data.answer || '(пустой ответ)', '#1a2a3a', '#8cf'); }) .catch(function(e) { if (btn) { btn.disabled = false; btn.textContent = '📖 LLM: Что делает?'; } showRes('Ошибка сети: ' + e.message, '#3a2a00', '#ffa'); }); } function parseMethods(v) { const items = String(v || '').split(',').map(s => s.trim().toUpperCase()).filter(Boolean); return items.length ? Array.from(new Set(items)) : ['GET']; } function parseTimeout(v) { var n = Number(String(v || '').trim()); if (!Number.isFinite(n) || n <= 0) return 60; return Math.round(n); } function onLangChange() { var lang = document.getElementById('c-lang').value; var t = LANG_TEMPLATES[lang]; if (t) { document.getElementById('c-entry').value = t.entrypoint; document.getElementById('c-code').value = t.code; } } function openCreate() { document.getElementById('c-lang').value = 'python'; onLangChange(); document.getElementById('c-timeout').value = '60'; document.getElementById('c-schedule-enabled').checked = false; document.getElementById('c-cron').value = ''; toggleScheduleFields('c'); document.getElementById('create-modal').classList.add('open'); document.getElementById('c-name').focus(); } function closeCreate() { document.getElementById('create-modal').classList.remove('open'); } async function submitCreate() { const btn = document.getElementById('c-submit'); btn.disabled = true; const progress = startTimedStatus('Создаём функцию...', 'Создание функции...', explainDelay); try { const name = document.getElementById('c-name').value.trim(); if (!name) throw new Error('name is required'); const lang = document.getElementById('c-lang').value.trim(); if (!lang) throw new Error('language is required'); await requestJSON(API_BASE + '/functions', 'POST', { name: name, language: lang, entrypoint: document.getElementById('c-entry').value.trim(), route: document.getElementById('c-route').value.trim(), methods: parseMethods(document.getElementById('c-methods').value), timeout: parseTimeout(document.getElementById('c-timeout').value), code: document.getElementById('c-code').value }); try { await syncScheduleForFunction(name, 'c'); } catch (scheduleErr) { try { await requestJSON(API_BASE + '/functions/' + encodeURIComponent(name), 'DELETE'); } catch (rollbackErr) { scheduleErr.message += '; rollback failed: ' + rollbackErr.message; } throw scheduleErr; } closeCreate(); progress.stop('Функция создана: ' + name, 'ok'); await reloadAll(); } catch (e) { progress.stop('Ошибка создания: ' + e.message, 'err'); } finally { btn.disabled = false; } } async function openEdit(name) { try { const fn = await getJSON(API_BASE + '/functions/' + encodeURIComponent(name)); S.currentEdit = fn; document.getElementById('e-title').textContent = 'Редактирование: ' + name; document.getElementById('e-name').value = name; document.getElementById('e-env').value = fn.environment || ''; document.getElementById('e-entry').value = fn.entrypoint || ''; document.getElementById('e-timeout').value = String(fn.timeout || 60); document.getElementById('e-code').value = fn.code || ''; var schedule = timeTriggerByFn(name); document.getElementById('e-schedule-enabled').checked = !!schedule; document.getElementById('e-cron').value = (schedule && schedule.spec && schedule.spec.cron) || ''; toggleScheduleFields('e'); var envName = (fn.environment || '').toLowerCase(); var lang = 'python'; if (envName.includes('node')) lang = 'nodejs'; else if (envName.includes('go')) lang = 'go'; else if (envName.includes('ruby')) lang = 'ruby'; else if (envName.includes('php')) lang = 'php'; document.getElementById('e-lang-hidden').value = lang; var aiRes = document.getElementById('e-ai-result'); if (aiRes) { aiRes.style.display = 'none'; aiRes.textContent = ''; } var warnEl = document.getElementById('e-tf-warn'); if (warnEl) { var isTf = /^tf-/.test(name) || /go[-_]env/.test(fn.environment || ''); warnEl.style.display = isTf ? 'block' : 'none'; } document.getElementById('edit-modal').classList.add('open'); } catch (e) { showStatus('Ошибка загрузки функции: ' + e.message, 'err'); } } function closeEdit() { document.getElementById('edit-modal').classList.remove('open'); S.currentEdit = null; } async function submitEdit() { if (!S.currentEdit) return; const btn = document.getElementById('e-submit'); btn.disabled = true; const progress = startTimedStatus('Сохраняем код...', 'Сохранение кода...', explainDelay); try { const name = S.currentEdit.name; await requestJSON(API_BASE + '/functions/' + encodeURIComponent(name) + '/code', 'PUT', { code: document.getElementById('e-code').value, timeout: parseTimeout(document.getElementById('e-timeout').value) }); await syncScheduleForFunction(name, 'e'); closeEdit(); progress.stop('Код обновлён: ' + name, 'ok'); await reloadAll(); } catch (e) { progress.stop('Ошибка обновления: ' + e.message, 'err'); } finally { btn.disabled = false; } } async function removeFn(name) { var tfWarn = (/^tf-/.test(name)) ? '\n\n⚠️ Эта функция управляется Terraform. Удаление приведёт к рассинхронизации state!' : ''; if (!confirm('Удалить функцию ' + name + '?' + tfWarn)) return; var progress = startTimedStatus('Удаляем функцию...', 'Удаление функции...', explainDelay); try { await requestJSON(API_BASE + '/functions/' + encodeURIComponent(name), 'DELETE'); progress.stop('Функция удалена: ' + name, 'ok'); await reloadAll(); } catch (e) { progress.stop('Ошибка удаления: ' + e.message, 'err'); } }