/* app.js — основная логика: загрузка данных, таблица функций */ async function reloadAll() { const progress = startTimedStatus('Загрузка...', 'Загрузка данных', null); // Показываем таблицу сразу с placeholder'ом document.getElementById('fn-rows').innerHTML = '⏳ Загружаем функции...'; 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); function makeFnRow(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 entrypoint = (spec.package && spec.package.functionName) || '-'; const timeout = spec.functionTimeout || 60; 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; const sourceType = ann['fission-console/source-type'] || 'code'; const status = ann['fission-console/status'] || 'Cold'; // Статус функции: иконка и цвет const statusColor = { 'Ready': '#2a2', 'Cold': '#22a', 'Building': '#aa2', 'Error': '#a22' }[status] || '#666'; const statusTitle = { 'Ready': 'Готова (есть pod)', 'Cold': 'Холодная (pod создаётся при первом вызове)', 'Building': 'Собирается / деплоится', 'Error': 'Ошибка (pod в CrashLoopBackOff или build failed)' }[status] || 'Неизвестно'; const statusDot = ''; const sourceIcon = sourceType === 'archive' ? '📦' : '📝'; var isGo = /go[-_]env/.test(env); var isTf = /^tf-/.test(name); var tfBadge = (isGo || isTf) ? 'TF ' : ''; // Сохраняем все данные в data-атрибуте для openInfo (избегаем повторного запроса) var infoData = h(JSON.stringify({ name: name, env: env, pkg: pkg, entrypoint: entrypoint, timeout: timeout, route: route, methods: methods, sourceType: sourceType, createdAt: createdAt, updatedAt: updatedAt, cron: cron, status: status })); var actions = tfBadge + ' ' + ' ' + ' ' + ' ' + ''; return '' + '' + h(name) + '' + '' + statusDot + '' + '' + sourceIcon + '' + '' + timestampCell(createdAt) + '' + '' + timestampCell(updatedAt) + '' + '' + h(route) + '' + '' + chips + '' + '' + cronCell + '' + '' + actions + '' + ''; } var fnList = fns || []; if (fnList.length === 0) { progress.stop('Загружено: нет функций', ''); document.getElementById('fn-rows').innerHTML = 'Нет функций'; } else { // Рендерим первую строку сразу — убираем placeholder var tbody = document.getElementById('fn-rows'); tbody.innerHTML = makeFnRow(fnList[0]); // Остальные добавляем по одной с небольшой задержкой var i = 1; function appendNext() { if (i >= fnList.length) { progress.stop('Загружено ' + fnList.length + ' функций', 'ok'); return; } var tr = document.createElement('tbody'); tr.innerHTML = makeFnRow(fnList[i]); tbody.appendChild(tr.firstChild); i++; setTimeout(appendNext, 40); } setTimeout(appendNext, 40); } } catch (e) { document.getElementById('fn-rows').innerHTML = 'Load error: ' + e.message + ''; progress.stop('Ошибка загрузки: ' + e.message, 'err'); } }