diff --git a/console/deploy/console.yaml b/console/deploy/console.yaml index 883701f..4129237 100644 --- a/console/deploy/console.yaml +++ b/console/deploy/console.yaml @@ -52,7 +52,7 @@ spec: serviceAccountName: fission-console containers: - name: console - image: naeel/fission-console:v1.3.45 + image: naeel/fission-console:v1.3.46 imagePullPolicy: Always ports: - containerPort: 8090 diff --git a/console/ui/index.html b/console/ui/index.html index c804f51..cfcca53 100644 --- a/console/ui/index.html +++ b/console/ui/index.html @@ -21,6 +21,8 @@ + + @@ -100,11 +102,12 @@
NUBES
FISSION CONSOLE
-
v1.3.45
+
v1.3.46
- + +
@@ -160,17 +163,18 @@ - diff --git a/console/ui/js/ai.js b/console/ui/js/ai.js index 56f8719..846eaf8 100644 --- a/console/ui/js/ai.js +++ b/console/ui/js/ai.js @@ -86,19 +86,21 @@ async function askAssistant() { msgs.scrollTop = msgs.scrollHeight; } -function showGenPrompt() { - var el = document.getElementById('c-gen-prompt'); +function showGenPrompt(prefix) { + prefix = prefix || 'cc'; + var el = document.getElementById(prefix + '-gen-prompt'); el.style.display = 'flex'; - document.getElementById('c-gen-desc').focus(); + document.getElementById(prefix + '-gen-desc').focus(); } -async function aiGenerate() { - var name = (document.getElementById('c-name').value || '').trim() || 'my-func'; - var lang = document.getElementById('c-lang').value || 'python'; - var desc = (document.getElementById('c-gen-desc').value || '').trim(); - if (!desc) { document.getElementById('c-gen-desc').focus(); return; } - var btn = document.getElementById('c-gen-btn'); - var resEl = document.getElementById('c-ai-result'); +async function aiGenerate(prefix) { + prefix = prefix || 'cc'; + var name = (document.getElementById(prefix + '-name').value || '').trim() || 'my-func'; + var lang = document.getElementById(prefix + '-lang').value || 'python'; + var desc = (document.getElementById(prefix + '-gen-desc').value || '').trim(); + if (!desc) { document.getElementById(prefix + '-gen-desc').focus(); return; } + var btn = document.getElementById(prefix + '-gen-btn'); + var resEl = document.getElementById(prefix + '-ai-result'); btn.disabled = true; btn.textContent = '⏳ Генерирую...'; resEl.style.display = 'block'; resEl.style.background = 'var(--bg-alt)'; resEl.style.color = 'var(--fg)'; resEl.textContent = 'Запрашиваю у LLM...'; @@ -110,8 +112,8 @@ async function aiGenerate() { description: desc }); var code = (d.answer || '').replace(/^```[\w]*\n?/, '').replace(/\n?```$/, ''); - document.getElementById('c-code').value = addLLMWarning(code, lang); - document.getElementById('c-gen-prompt').style.display = 'none'; + document.getElementById(prefix + '-code').value = addLLMWarning(code, lang); + document.getElementById(prefix + '-gen-prompt').style.display = 'none'; resEl.style.background = '#1a3a1a'; resEl.style.color = '#8f8'; resEl.textContent = 'Код сгенерирован и вставлен.'; } catch (e) { diff --git a/console/ui/js/fn-archive.js b/console/ui/js/fn-archive.js new file mode 100644 index 0000000..2a41020 --- /dev/null +++ b/console/ui/js/fn-archive.js @@ -0,0 +1,83 @@ +/* fn-archive.js — создание функции из архива (независимый модуль) */ + +function onLangChangeArchive() { + var lang = document.getElementById('ca-lang').value; + var t = LANG_TEMPLATES[lang]; + if (t) { + document.getElementById('ca-entry').value = t.entrypoint; + } +} + +function openCreateArchive() { + document.getElementById('ca-lang').value = 'python'; + onLangChangeArchive(); + document.getElementById('ca-name').value = ''; + document.getElementById('ca-route').value = ''; + document.getElementById('ca-methods').value = 'GET'; + document.getElementById('ca-timeout').value = '60'; + document.getElementById('ca-schedule-enabled').checked = false; + document.getElementById('ca-cron').value = ''; + toggleScheduleFields('ca'); + var fileInput = document.getElementById('ca-archive-file'); + if (fileInput) fileInput.value = ''; + var aiRes = document.getElementById('ca-ai-result'); + if (aiRes) { aiRes.style.display = 'none'; aiRes.textContent = ''; } + document.getElementById('create-archive-modal').classList.add('open'); + document.getElementById('ca-name').focus(); +} + +function closeCreateArchive() { + document.getElementById('create-archive-modal').classList.remove('open'); +} + +async function submitCreateArchive() { + const btn = document.getElementById('ca-submit'); + btn.disabled = true; + const progress = startTimedStatus('Создаём функцию...', 'Создание функции...', explainDelay); + try { + const name = document.getElementById('ca-name').value.trim(); + if (!name) throw new Error('name is required'); + const lang = document.getElementById('ca-lang').value.trim(); + if (!lang) throw new Error('language is required'); + const archiveFile = document.getElementById('ca-archive-file').files[0]; + if (!archiveFile) throw new Error('выберите .zip архив'); + + var fd = new FormData(); + fd.append('name', name); + fd.append('language', lang); + fd.append('entrypoint', document.getElementById('ca-entry').value.trim()); + fd.append('route', document.getElementById('ca-route').value.trim()); + fd.append('methods', document.getElementById('ca-methods').value.trim()); + fd.append('timeout', String(parseTimeout(document.getElementById('ca-timeout').value))); + fd.append('archive', archiveFile); + + var resp = await fetch(API_BASE + '/functions', { + method: 'POST', + headers: authHeaders(), + body: fd + }); + if (!resp.ok) { + var e = await resp.json().catch(() => ({})); + throw new Error(e.error || resp.statusText); + } + + try { + await syncScheduleForFunction(name, 'ca'); + } catch (scheduleErr) { + try { + await requestJSON(API_BASE + '/functions/' + encodeURIComponent(name), 'DELETE'); + } catch (rollbackErr) { + scheduleErr.message += '; rollback failed: ' + rollbackErr.message; + } + throw scheduleErr; + } + + closeCreateArchive(); + progress.stop('Функция создана: ' + name, 'ok'); + await reloadAll(); + } catch (e) { + progress.stop('Ошибка создания: ' + e.message, 'err'); + } finally { + btn.disabled = false; + } +} diff --git a/console/ui/js/fn-code.js b/console/ui/js/fn-code.js new file mode 100644 index 0000000..61d8748 --- /dev/null +++ b/console/ui/js/fn-code.js @@ -0,0 +1,71 @@ +/* fn-code.js — создание функции из кода (независимый модуль) */ + +function onLangChangeCode() { + var lang = document.getElementById('cc-lang').value; + var t = LANG_TEMPLATES[lang]; + if (t) { + document.getElementById('cc-entry').value = t.entrypoint; + document.getElementById('cc-code').value = t.code; + } +} + +function openCreateCode() { + document.getElementById('cc-lang').value = 'python'; + onLangChangeCode(); + document.getElementById('cc-name').value = ''; + document.getElementById('cc-route').value = ''; + document.getElementById('cc-methods').value = 'GET'; + document.getElementById('cc-timeout').value = '60'; + document.getElementById('cc-schedule-enabled').checked = false; + document.getElementById('cc-cron').value = ''; + toggleScheduleFields('cc'); + var aiRes = document.getElementById('cc-ai-result'); + if (aiRes) { aiRes.style.display = 'none'; aiRes.textContent = ''; } + document.getElementById('create-code-modal').classList.add('open'); + document.getElementById('cc-name').focus(); +} + +function closeCreateCode() { + document.getElementById('create-code-modal').classList.remove('open'); +} + +async function submitCreateCode() { + const btn = document.getElementById('cc-submit'); + btn.disabled = true; + const progress = startTimedStatus('Создаём функцию...', 'Создание функции...', explainDelay); + try { + const name = document.getElementById('cc-name').value.trim(); + if (!name) throw new Error('name is required'); + const lang = document.getElementById('cc-lang').value.trim(); + if (!lang) throw new Error('language is required'); + + await requestJSON(API_BASE + '/functions', 'POST', { + name: name, + language: lang, + entrypoint: document.getElementById('cc-entry').value.trim(), + route: document.getElementById('cc-route').value.trim(), + methods: parseMethods(document.getElementById('cc-methods').value), + timeout: parseTimeout(document.getElementById('cc-timeout').value), + code: document.getElementById('cc-code').value + }); + + try { + await syncScheduleForFunction(name, 'cc'); + } catch (scheduleErr) { + try { + await requestJSON(API_BASE + '/functions/' + encodeURIComponent(name), 'DELETE'); + } catch (rollbackErr) { + scheduleErr.message += '; rollback failed: ' + rollbackErr.message; + } + throw scheduleErr; + } + + closeCreateCode(); + progress.stop('Функция создана: ' + name, 'ok'); + await reloadAll(); + } catch (e) { + progress.stop('Ошибка создания: ' + e.message, 'err'); + } finally { + btn.disabled = false; + } +} diff --git a/console/ui/js/functions.js b/console/ui/js/functions.js index 85fd5bb..10b0942 100644 --- a/console/ui/js/functions.js +++ b/console/ui/js/functions.js @@ -133,89 +133,7 @@ function parseTimeout(v) { 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'); - - // Определяем режим: archiveArea видна → archive mode - var archiveArea = document.getElementById('c-archive-area'); - var isArchiveMode = archiveArea && archiveArea.style.display !== 'none'; - var archiveFile = isArchiveMode ? document.getElementById('c-archive-file').files[0] : null; - - if (isArchiveMode && archiveFile) { - // Отправляем multipart/form-data с архивом - var fd = new FormData(); - fd.append('name', name); - fd.append('language', lang); - fd.append('entrypoint', document.getElementById('c-entry').value.trim()); - fd.append('route', document.getElementById('c-route').value.trim()); - fd.append('methods', document.getElementById('c-methods').value.trim()); - fd.append('timeout', String(parseTimeout(document.getElementById('c-timeout').value))); - fd.append('archive', archiveFile); - var resp = await fetch(API_BASE + '/functions', { method: 'POST', headers: authHeaders(), body: fd }); - if (!resp.ok) { var e = await resp.json().catch(() => ({})); throw new Error(e.error || resp.statusText); } - } else { - 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; - } -} +// openCreate/closeCreate/submitCreate перенесены в fn-code.js и fn-archive.js async function openEdit(name) { try { diff --git a/doc/plans/separate-create-modals.md b/doc/plans/separate-create-modals.md new file mode 100644 index 0000000..1f837f1 --- /dev/null +++ b/doc/plans/separate-create-modals.md @@ -0,0 +1,75 @@ +# План: разделение Create на два независимых модуля +Ветка: `separate-create-modals` +Дата: 2026-05-04 + +--- + +## Цель +Разделить одну кнопку «Создать функцию» с переключателем код/архив на два полностью независимых модуля. +Никакого общего состояния, никакого общего if/else. + +--- + +## Задачи + +### 1. Создать `console/ui/js/fn-code.js` [ ] +Содержит только: +- `onLangChangeCode()` — читает `cc-lang`, пишет в `cc-entry`, `cc-code` +- `openCreateCode()` — инит формы, открывает `create-code-modal` +- `closeCreateCode()` — закрывает `create-code-modal` +- `submitCreateCode()` — отправляет JSON `POST /functions`, без архивной ветки + +### 2. Создать `console/ui/js/fn-archive.js` [ ] +Содержит только: +- `onLangChangeArchive()` — читает `ca-lang`, пишет в `ca-entry` +- `openCreateArchive()` — инит формы, открывает `create-archive-modal` +- `closeCreateArchive()` — закрывает `create-archive-modal` +- `submitCreateArchive()` — отправляет multipart `POST /functions`, без кодовой ветки + +### 3. Изменить `index.html` [ ] +- Заменить 1 кнопку «+ Создать функцию» на две: + - `+ Из кода` → `openCreateCode()` + - `+ Из архива` → `openCreateArchive()` +- Добавить `create-code-modal` (prefix `cc-`) — только textarea + AI-кнопки +- Добавить `create-archive-modal` (prefix `ca-`) — только file input + lint/explain +- Удалить старую `create-modal` с переключателями +- Подключить `