// Модальные окна 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; } function openHelp() { document.getElementById('help-modal').classList.add('open'); } function closeHelp() { document.getElementById('help-modal').classList.remove('open'); } 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; } } function openInvoke(name) { S.currentInvoke = name; document.getElementById('i-title').textContent = 'Вызов: ' + name; document.getElementById('i-resp').value = ''; document.getElementById('i-status').textContent = ''; document.getElementById('i-meta').style.display = 'none'; document.getElementById('i-meta').textContent = ''; document.getElementById('invoke-modal').classList.add('open'); } function closeInvoke() { document.getElementById('invoke-modal').classList.remove('open'); S.currentInvoke = null; } async function submitInvoke() { if (!S.currentInvoke) return; const btn = document.getElementById('i-submit'); const statusEl = document.getElementById('i-status'); const metaEl = document.getElementById('i-meta'); const respEl = document.getElementById('i-resp'); btn.disabled = true; respEl.value = ''; metaEl.style.display = 'none'; metaEl.textContent = ''; let elapsed = 0; statusEl.textContent = 'Вызов...'; const timer = setInterval(() => { elapsed++; if (elapsed < 5) { statusEl.textContent = 'Вызов... ' + elapsed + 'с'; } else if (elapsed < 10) { statusEl.textContent = '⏳ Возможен cold start — прогрев пула... ' + elapsed + 'с'; } else { statusEl.textContent = '⏳ Возможны cold start или specialization... ' + elapsed + 'с'; } }, 1000); try { const raw = document.getElementById('i-body').value.trim(); let parsed = {}; if (raw) parsed = JSON.parse(raw); const result = await requestJSON(API_BASE + '/functions/' + encodeURIComponent(S.currentInvoke) + '/invoke', 'POST', parsed); var latencyMs = Number(result.latency_ms || 0); var measuredSeconds = latencyMs > 0 ? Math.max(1, Math.round(latencyMs / 1000)) : elapsed; statusEl.textContent = '✓ Выполнено за ' + formatSeconds(measuredSeconds); metaEl.style.display = 'block'; metaEl.textContent = [ 'HTTP status: ' + (result.status || 'n/a'), 'Latency: ' + (latencyMs || 'n/a') + ' ms', 'Причина задержки: ' + explainDelay(measuredSeconds), 'Invoke URL: ' + (result.invoke_url || 'n/a') ].join('\n'); respEl.value = JSON.stringify(result, null, 2); } catch (e) { statusEl.textContent = '✗ Ошибка после ' + formatSeconds(elapsed); metaEl.style.display = 'block'; metaEl.textContent = [ 'Последняя ошибка: ' + e.message, 'Вероятная причина задержки: ' + explainDelay(elapsed) ].join('\n'); respEl.value = 'Ошибка вызова: ' + e.message; } finally { clearInterval(timer); 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'); } }