// Главный модуль приложения Console // Содержит всю логику UI, модальные окна, API запросы const API_BASE = window.location.pathname.startsWith('/console') ? '/console/api' : '/api'; const S = { envs: [], fns: [], httpTriggers: [], timeTriggers: [], currentEdit: null, currentInvoke: null }; function authHeaders() { return { 'X-Auth-Token': localStorage.getItem('auth_token') || '', 'X-Auth-Env': localStorage.getItem('auth_env') || 'test' }; } async function getJSON(url) { // Функция перемещена в api.js } async function requestJSON(url, method, body) { // Функция перемещена в api.js } function showStatus(message, kind) { // Функция перемещена в ui.js } function clearStatus() { // Функция перемещена в ui.js } function setText(id, value) { // Функция перемещена в ui.js } 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 llmGeneratedWarning(lang) { var text = 'Сделано LLM. Не доверяй, проверяй!'; switch (lang) { case 'php': return '// ' + text; case 'nodejs': case 'go': return '// ' + text; case 'ruby': case 'python': default: return '# ' + text; } } function addLLMWarning(code, lang) { var body = String(code || '').replace(/^\s+/, ''); var warning = llmGeneratedWarning(lang); if (!body) return warning; if (body.indexOf(warning) === 0) return body; if (lang === 'php' && body.indexOf(' { 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'); } } async function reloadAll() { try { const [envs, pkgs, fns, http, times] = await Promise.all([ getJSON(API_BASE + '/environments'), getJSON(API_BASE + '/packages'), getJSON(API_BASE + '/functions'), getJSON(API_BASE + '/httptriggers'), getJSON(API_BASE + '/timetriggers') ]); S.envs = envs || []; S.fns = fns || []; S.httpTriggers = http || []; S.timeTriggers = times || []; setText('env-count', envs.length || 0); setText('pkg-count', pkgs.length || 0); setText('fn-count', fns.length || 0); setText('http-count', http.length || 0); setText('cron-count', new Set((times || []).map(function (t) { return t && t.spec && t.spec.functionref && t.spec.functionref.name; }).filter(Boolean)).size); const rows = (fns || []).map(function (f) { const spec = f.spec || {}; const meta = f.metadata || {}; const ann = meta.annotations || {}; const env = (spec.environment && spec.environment.name) || '-'; const pkg = (spec.package && spec.package.packageref && spec.package.packageref.name) || '-'; const name = (f.metadata && f.metadata.name) || '-'; const trig = httpTriggerByFn(name) || {}; const timeTrig = timeTriggerByFn(name) || {}; const route = (trig.spec && trig.spec.relativeurl) || '-'; const methods = (trig.spec && trig.spec.methods) || []; const chips = methods.map(function (m) { return '' + h(m) + ''; }).join(''); const cron = (timeTrig.spec && timeTrig.spec.cron) || ''; const cronCell = cron ? '' + h(cron) + '' : ''; const createdAt = ann['fission-console/created-at'] || meta.creationTimestamp || '-'; const updatedAt = ann['fission-console/updated-at'] || createdAt; var isGo = /go[-_]env/.test(env); var isTf = /^tf-/.test(name); var tfBadge = (isGo || isTf) ? 'TF ' : ''; var actions = tfBadge + ' ' + ' ' + ''; return '' + '' + h(name) + '' + '' + h(env) + '' + '' + h(pkg) + '' + '' + timestampCell(createdAt) + '' + '' + timestampCell(updatedAt) + '' + '' + h(route) + '' + '' + chips + '' + '' + cronCell + '' + '' + actions + '' + ''; }).join(''); document.getElementById('fn-rows').innerHTML = rows || 'Нет функций'; } catch (e) { document.getElementById('fn-rows').innerHTML = 'Load error: ' + e.message + ''; showStatus('Ошибка загрузки: ' + e.message, 'err'); } } // Инициализация и загрузка модулей fetch('templates/modals/login.html') .then(r => r.text()) .then(html => { document.body.insertAdjacentHTML('afterbegin', html); }); // Инициализация функций async function aiCheck(codeId, langId, resultId) { // Функция перемещена в ai.js — не удаляй, это placeholder для обратной совместимости console.error('aiCheck должна быть вызвана из ai.js, но она не загружена!'); } // Init polling (перемещено в init.js) var _initPolling = false; var _initTickBusy = false; var _initStartedAt = 0; function initLog(msg) { // Функция перемещена в init.js } function formatSeconds(totalSeconds) { // Функция перемещена в init.js } function setInitElapsed(seconds) { // Функция перемещена в init.js } function setInitCurrentStage(text) { // Функция перемещена в init.js } function setBox(id, text) { // Функция перемещена в init.js } function missingKeys(obj) { // Функция перемещена в init.js } function firstPendingStage(stages) { // Функция перемещена в init.js } function describeInitReason(debug) { // Функция перемещена в init.js } function summarizeInit(status, debug) { // Функция перемещена в init.js } function explainDelay(seconds) { // Функция перемещена в init.js } function startTimedStatus(startText, waitingPrefix, waitingHint) { // Функция перемещена в init.js } function renderInitStages(stages) { // Функция перемещена в init.js } async function pollNSStatus() { // Функция перемещена в init.js } function checkAuth() { var storedToken = localStorage.getItem('auth_token'); if (!storedToken) { showLoginOverlay(); return; } setUserAvatar(storedToken); hideLoginOverlay(); reloadAll(); } // AI Ассистент (функции перемещены в ui.js) var _assistantOpen = true; function toggleAssistant() { // Функция перемещена в ui.js } async function askAssistant() { // Функция перемещена в ui.js } function showGenPrompt() { // Функция перемещена в ai.js } async function aiGenerate() { // Функция перемещена в ai.js } async function aiExplain(codeId, langId, resultId) { // Функция перемещена в ai.js } // Event delegation для data-* атрибутов function initializeEventListeners() { document.addEventListener('click', function(e) { const target = e.target.closest('[data-onclick]'); if (target) { const handler = target.dataset.onclick; try { // Попытаемся вызвать как функцию или выполнить как код eval(`${handler}`); } catch (err) { console.error('Error in onclick handler:', handler, err); } } }); document.addEventListener('change', function(e) { const target = e.target.closest('[data-onchange]'); if (target) { const handler = target.dataset.onchange; try { eval(`${handler}`); } catch (err) { console.error('Error in onchange handler:', handler, err); } } }); document.addEventListener('keydown', function(e) { const target = e.target.closest('[data-onkeydown]'); if (target) { const expr = target.dataset.onkeydown; try { // Выполняем выражение с доступом к event eval(`(function(event) { ${expr} }).call(this, e)`); } catch (err) { console.error('Error in onkeydown handler:', err); } } }); } // Инициализация приложения initializeEventListeners(); checkAuth();