v1.3.27: fix Help modal — add -L flag, demo-login docs

This commit is contained in:
Naeel
2026-05-03 06:46:27 +03:00
parent d7352532dc
commit df74a69fde
37 changed files with 1179 additions and 2497 deletions
+59 -1
View File
@@ -1,4 +1,32 @@
// AI-функции (линтер, генерация, объяснение)
/* ai.js — AI/LLM функции: проверка, генерация, объяснение, ассистент */
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('<?php') === 0) {
var rest = body.slice(5);
rest = rest.replace(/^\r?\n/, '');
return '<?php\n' + warning + '\n' + rest;
}
return warning + '\n' + body;
}
async function aiCheck(codeId, langId, resultId) {
var code = document.getElementById(codeId).value.trim();
@@ -28,6 +56,36 @@ async function aiCheck(codeId, langId, resultId) {
}
}
var _assistantOpen = true;
function toggleAssistant() {
_assistantOpen = !_assistantOpen;
document.getElementById('assistant-body').style.display = _assistantOpen ? 'flex' : 'none';
document.getElementById('assistant-toggle-icon').textContent = _assistantOpen ? '▲' : '▼';
}
async function askAssistant() {
var inp = document.getElementById('assistant-input');
var msgs = document.getElementById('assistant-messages');
var q = inp.value.trim();
if (!q) return;
inp.value = '';
msgs.textContent += '\n\n👤 ' + q + '\n⏳ ...';
msgs.scrollTop = msgs.scrollHeight;
try {
var r = await fetch('/console/api/ai/ask', {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders() },
body: JSON.stringify({ mode: 'chat', question: q })
});
var d = await r.json();
msgs.textContent = msgs.textContent.replace('⏳ ...', '🤖 ' + (d.answer || d.error || 'Нет ответа'));
} catch (e) {
msgs.textContent = msgs.textContent.replace('⏳ ...', '❌ Ошибка: ' + e.message);
}
msgs.scrollTop = msgs.scrollHeight;
}
function showGenPrompt() {
var el = document.getElementById('c-gen-prompt');
el.style.display = 'flex';
+8 -1
View File
@@ -1,4 +1,11 @@
// API-вызовы и авторизация
/* api.js — HTTP-утилиты */
function authHeaders() {
return {
'X-Auth-Token': localStorage.getItem('auth_token') || '',
'X-Auth-Env': localStorage.getItem('auth_env') || 'test'
};
}
async function getJSON(url) {
const r = await fetch(url, { headers: authHeaders() });
+7 -418
View File
@@ -1,269 +1,4 @@
// Главный модуль приложения 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('<?php') === 0) {
var rest = body.slice(5);
rest = rest.replace(/^\r?\n/, '');
return '<?php\n' + warning + '\n' + rest;
}
return warning + '\n' + body;
}
function httpTriggerByFn(fnName) {
// Функция перемещена в triggers.js
}
function timeTriggerByFn(fnName) {
// Функция перемещена в triggers.js
}
function toggleScheduleFields(prefix) {
// Функция перемещена в triggers.js
}
function schedulePayload(prefix) {
// Функция перемещена в triggers.js
}
async function syncScheduleForFunction(name, prefix) {
// Функция перемещена в triggers.js
}
function h(v) {
// Функция перемещена в ui.js
return String(v == null ? '' : v);
}
function formatTimestamp(ts) {
// Функция перемещена в ui.js
}
function timestampCell(ts) {
// Функция перемещена в ui.js
}
const LANG_TEMPLATES = {
// Константы перемещены в config.js
};
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');
}
}
/* app.js — основная логика: загрузка данных, таблица функций */
async function reloadAll() {
try {
@@ -308,9 +43,9 @@ async function reloadAll() {
var isTf = /^tf-/.test(name);
var tfBadge = (isGo || isTf) ? '<span class="chip" style="background:#555;color:#ffa" title="Управляется Terraform. Изменения могут быть перезаписаны при terraform apply.">TF</span> ' : '';
var actions = tfBadge +
'<button class="btn ghost" onclick="openEdit(\'' + h(name) + '\')">\u0420\u0435\u0434.</button> ' +
'<button class="btn ghost" onclick="openInvoke(\'' + h(name) + '\')">\u0412\u044b\u0437\u043e\u0432</button> ' +
'<button class="btn danger" onclick="removeFn(\'' + h(name) + '\')">\u0423\u0434\u0430\u043b\u0438\u0442\u044c</button>';
'<button class="btn ghost" onclick="openEdit(\'' + h(name) + '\')">Ред.</button> ' +
'<button class="btn ghost" onclick="openInvoke(\'' + h(name) + '\')">Вызов</button> ' +
'<button class="btn danger" onclick="removeFn(\'' + h(name) + '\')">Удалить</button>';
return '<tr>' +
'<td class="mono">' + h(name) + '</td>' +
'<td>' + h(env) + '</td>' +
@@ -325,157 +60,11 @@ async function reloadAll() {
}).join('');
document.getElementById('fn-rows').innerHTML = rows || '<tr><td colspan="9">Нет функций</td></tr>';
if (!rows) {
document.getElementById('fn-rows').innerHTML = '<tr><td colspan="9">Нет функций</td></tr>';
}
} catch (e) {
document.getElementById('fn-rows').innerHTML = '<tr><td colspan="9">Load error: ' + e.message + '</td></tr>';
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();
+31 -9
View File
@@ -1,4 +1,4 @@
// Логика авторизации (логин/логаут, токен, email)
/* auth.js — авторизация */
function showLoginOverlay() {
document.getElementById('login-overlay').style.display = 'flex';
@@ -13,7 +13,9 @@ async function doLogin() {
var errEl = document.getElementById('l-error');
var token = (document.getElementById('l-token').value || '').trim();
var env = document.getElementById('l-env').value;
if (!token) { errEl.textContent = 'Введите токен'; errEl.style.display = 'block'; return; }
if (!token) { errEl.textContent = 'Введите токен или логин'; errEl.style.display = 'block'; return; }
var isJWT = token.split('.').length === 3 && !token.includes(' ');
if (!isJWT && token.length < 6) { errEl.textContent = 'Логин — не менее 6 символов'; errEl.style.display = 'block'; return; }
btn.disabled = true;
errEl.style.display = 'none';
try {
@@ -26,8 +28,11 @@ async function doLogin() {
const d = await res.json().catch(function () { return {}; });
throw new Error(d.error || 'Ошибка входа');
}
const data = await res.json();
localStorage.setItem('auth_token', token);
localStorage.setItem('auth_env', env);
if (data.email) localStorage.setItem('auth_email', data.email);
else localStorage.removeItem('auth_email');
setUserAvatar(token);
hideLoginOverlay();
// Проверяем статус NS — если не ready, показываем init overlay
@@ -58,26 +63,43 @@ function jwtEmail(token) {
function setUserAvatar(token) {
var el = document.getElementById('user-avatar');
if (!el) return;
var email = token ? jwtEmail(token) : null;
var email = localStorage.getItem('auth_email') || (token ? jwtEmail(token) : null);
if (email) {
el.textContent = email;
el.title = email;
el.style.fontSize = '0.7rem';
el.style.minWidth = '60px';
el.style.padding = '0 8px';
el.style.fontSize = '0.75rem';
el.style.minWidth = 'auto';
el.style.padding = '0 10px';
el.style.background = 'none';
el.style.border = '1px solid var(--border)';
el.style.borderRadius = '6px';
el.style.color = 'var(--text-primary)';
el.style.fontWeight = '400';
el.style.width = 'auto';
el.style.height = '30px';
} else {
el.textContent = 'N';
el.title = '';
el.style.fontSize = '';
el.style.minWidth = '';
el.style.padding = '';
el.style.cssText = '';
}
}
function doLogout() {
localStorage.removeItem('auth_token');
localStorage.removeItem('auth_env');
localStorage.removeItem('auth_email');
try { document.getElementById('l-token').value = ''; } catch (_) { }
setUserAvatar(null);
showLoginOverlay();
}
function checkAuth() {
var storedToken = localStorage.getItem('auth_token');
if (!storedToken) {
showLoginOverlay();
return;
}
setUserAvatar(storedToken);
hideLoginOverlay();
reloadAll();
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Константы и настройки
/* config.js — константы и шаблоны языков */
const LANG_TEMPLATES = {
python: {
+89 -3
View File
@@ -1,4 +1,15 @@
// CRUD-операции с функциями (создание, редактирование, удаление)
/* functions.js — CRUD операции с функциями */
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;
@@ -56,11 +67,86 @@ async function submitCreate() {
}
closeCreate();
progress.stop('\u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0430: ' + name, 'ok');
progress.stop('Функция создана: ' + name, 'ok');
await reloadAll();
} catch (e) {
progress.stop('\u041e\u0448\u0438\u0431\u043a\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f: ' + e.message, 'err');
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');
}
}
+2 -3
View File
@@ -1,6 +1,5 @@
// Инициализация окружения
/* init.js — инициализация namespace, утилиты таймера и статуса */
// Init polling
var _initPolling = false;
var _initTickBusy = false;
var _initStartedAt = 0;
@@ -136,7 +135,7 @@ async function pollNSStatus() {
setBox('init-error', '');
initLog('Запуск инициализации...');
var attempt = 0;
var maxAttempts = 60;
var maxAttempts = 60; // 5 минут
var interval = setInterval(async function () {
if (_initTickBusy) return;
_initTickBusy = true;
+1 -76
View File
@@ -1,43 +1,4 @@
// Модальные окна
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;
}
/* modals.js — управление модальными окнами Help и Invoke */
function openHelp() {
document.getElementById('help-modal').classList.add('open');
@@ -47,29 +8,6 @@ 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;
@@ -136,16 +74,3 @@ async function submitInvoke() {
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');
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// HTTP и Cron триггеры
/* triggers.js — HTTP и Cron триггеры */
function httpTriggerByFn(fnName) {
return (S.httpTriggers || []).find(function (t) {
+5 -35
View File
@@ -1,4 +1,8 @@
// UI-вспомогательные функции
/* ui.js — UI-вспомогательные функции */
function setText(id, value) {
document.getElementById(id).textContent = String(value);
}
function showStatus(message, kind) {
const el = document.getElementById('status');
@@ -14,10 +18,6 @@ function clearStatus() {
el.textContent = '';
}
function setText(id, value) {
document.getElementById(id).textContent = String(value);
}
function h(v) {
return String(v == null ? '' : v)
.replaceAll('&', '&amp;')
@@ -45,33 +45,3 @@ function timestampCell(ts) {
var text = formatTimestamp(ts);
return '<span class="mono" title="' + h(ts || '') + '">' + h(text) + '</span>';
}
// AI Ассистент
var _assistantOpen = true;
function toggleAssistant() {
_assistantOpen = !_assistantOpen;
document.getElementById('assistant-body').style.display = _assistantOpen ? 'flex' : 'none';
document.getElementById('assistant-toggle-icon').textContent = _assistantOpen ? '▲' : '▼';
}
async function askAssistant() {
var inp = document.getElementById('assistant-input');
var msgs = document.getElementById('assistant-messages');
var q = inp.value.trim();
if (!q) return;
inp.value = '';
msgs.textContent += '\n\n👤 ' + q + '\n⏳ ...';
msgs.scrollTop = msgs.scrollHeight;
try {
var r = await fetch('/console/api/ai/ask', {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders() },
body: JSON.stringify({ mode: 'chat', question: q })
});
var d = await r.json();
msgs.textContent = msgs.textContent.replace('⏳ ...', '🤖 ' + (d.answer || d.error || 'Нет ответа'));
} catch (e) {
msgs.textContent = msgs.textContent.replace('⏳ ...', '❌ Ошибка: ' + e.message);
}
msgs.scrollTop = msgs.scrollHeight;
}