v1.3.46: separate create modals — code (cc-) and archive (ca-) as independent modules
This commit is contained in:
@@ -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
|
||||
|
||||
+100
-40
@@ -21,6 +21,8 @@
|
||||
<script src="js/modals.js"></script>
|
||||
<script src="js/init.js"></script>
|
||||
<script src="js/functions.js"></script>
|
||||
<script src="js/fn-code.js"></script>
|
||||
<script src="js/fn-archive.js"></script>
|
||||
<script src="js/ai.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</head>
|
||||
@@ -100,11 +102,12 @@
|
||||
<div class="nubes">NUBES</div>
|
||||
<div class="product">FISSION CONSOLE</div>
|
||||
</div>
|
||||
<div style="font-size:0.65rem; color:var(--text-secondary); margin-left:10px; align-self:center; opacity:0.7;">v1.3.45</div>
|
||||
<div style="font-size:0.65rem; color:var(--text-secondary); margin-left:10px; align-self:center; opacity:0.7;">v1.3.46</div>
|
||||
</div>
|
||||
<div class="row" style="margin:0;">
|
||||
<button class="btn ghost" onclick="reloadAll()">Refresh</button>
|
||||
<button class="btn" onclick="openCreate()">+ Создать функцию</button>
|
||||
<button class="btn" onclick="openCreateCode()">✏️ Из кода</button>
|
||||
<button class="btn" onclick="openCreateArchive()">📦 Из архива</button>
|
||||
<button class="btn ghost" onclick="openHelp()">Help</button>
|
||||
<button class="btn ghost" onclick="doLogout()" style="margin-left:8px;">Выход</button>
|
||||
</div>
|
||||
@@ -160,17 +163,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="create-modal" class="modal">
|
||||
<!-- Модалка: создать функцию из кода (prefix cc-) -->
|
||||
<div id="create-code-modal" class="modal">
|
||||
<div class="panel">
|
||||
<h3>Создать функцию</h3>
|
||||
<h3>✏️ Создать функцию из кода</h3>
|
||||
<div class="row">
|
||||
<div class="field">
|
||||
<label>Name</label>
|
||||
<input id="c-name" placeholder="demo-fn">
|
||||
<input id="cc-name" placeholder="demo-fn">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Language</label>
|
||||
<select id="c-lang" onchange="onLangChange()">
|
||||
<select id="cc-lang" onchange="onLangChangeCode()">
|
||||
<option value="python">Python</option>
|
||||
<option value="nodejs">Node.js</option>
|
||||
<option value="php">PHP</option>
|
||||
@@ -179,73 +183,129 @@
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Entrypoint</label>
|
||||
<input id="c-entry" value="main.main">
|
||||
<input id="cc-entry" value="main.main">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="field">
|
||||
<label>Route</label>
|
||||
<input id="c-route" placeholder="/demo-fn">
|
||||
<input id="cc-route" placeholder="/demo-fn">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Методы (через запятую)</label>
|
||||
<input id="c-methods" value="GET">
|
||||
<input id="cc-methods" value="GET">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Timeout (сек)</label>
|
||||
<input id="c-timeout" type="number" min="1" value="60">
|
||||
<input id="cc-timeout" type="number" min="1" value="60">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="field" style="min-width:240px; flex:0 0 260px;">
|
||||
<label style="display:flex; align-items:center; gap:8px; color:var(--text-primary); margin-top:18px;">
|
||||
<input id="c-schedule-enabled" type="checkbox" onchange="toggleScheduleFields('c')" style="width:auto;">
|
||||
<input id="cc-schedule-enabled" type="checkbox" onchange="toggleScheduleFields('cc')" style="width:auto;">
|
||||
Выполнять по расписанию
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Cron</label>
|
||||
<input id="c-cron" placeholder="*/5 * * * *" disabled>
|
||||
<input id="cc-cron" placeholder="*/5 * * * *" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="display:flex; gap:4px; margin-bottom:6px;">
|
||||
<button class="btn ghost" id="c-mode-code" onclick="setCodeMode('c','code')" style="font-size:.8rem; padding:3px 10px;">✏️ Код</button>
|
||||
<button class="btn ghost" id="c-mode-archive" onclick="setCodeMode('c','archive')" style="font-size:.8rem; padding:3px 10px;">📦 Архив (.zip)</button>
|
||||
</div>
|
||||
<div id="c-code-area">
|
||||
<textarea id="c-code">def main():
|
||||
<div id="cc-code-area">
|
||||
<textarea id="cc-code">def main():
|
||||
return {"ok": True, "msg": "hello from fission console"}
|
||||
</textarea>
|
||||
<div style="margin-top:6px; display:flex; gap:6px; flex-wrap:wrap;">
|
||||
<button class="btn ghost" id="c-ai-btn" onclick="aiCheck('c-code','c-lang','c-ai-result')">🔍 Проверить
|
||||
синтаксис линтером</button>
|
||||
<button class="btn ghost" id="c-gen-btn" onclick="showGenPrompt()">✨ Сгенерировать код LLM</button>
|
||||
<button class="btn ghost" id="c-exp-btn" onclick="aiExplain('c-code','c-lang','c-ai-result')">📖 LLM:
|
||||
Что делает?</button>
|
||||
<button class="btn ghost" id="cc-ai-btn" onclick="aiCheck('cc-code','cc-lang','cc-ai-result')">🔍 Проверить синтаксис линтером</button>
|
||||
<button class="btn ghost" id="cc-gen-btn" onclick="showGenPrompt('cc')">✨ Сгенерировать код LLM</button>
|
||||
<button class="btn ghost" id="cc-exp-btn" onclick="aiExplain('cc-code','cc-lang','cc-ai-result')">📖 LLM: Что делает?</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="c-archive-area" style="display:none;">
|
||||
<input type="file" id="c-archive-file" accept=".zip" style="display:block; margin-bottom:8px; color:var(--text-primary);">
|
||||
<div style="display:flex; gap:6px; flex-wrap:wrap;">
|
||||
<button id="c-lint-btn" class="btn ghost" onclick="lintArchiveFile('c')">🔍 Проверка архива линтером</button>
|
||||
<button id="c-explain-archive-btn" class="btn ghost" onclick="explainArchiveFile('c')">📖 LLM: Что делает?</button>
|
||||
</div>
|
||||
<div id="cc-gen-prompt" style="display:none; margin-top:8px; gap:6px; align-items:center;">
|
||||
<input id="cc-gen-desc" type="text"
|
||||
placeholder="Что должна делать функция? (напр: принять JSON, вернуть сумму чисел)"
|
||||
style="flex:1; background:#2a2a42; border:1px solid #3a3a5c; border-radius:4px; color:#cdd6f4; padding:6px 8px; font-size:.82rem; outline:none; width:100%;"
|
||||
onkeydown="if(event.key==='Enter')aiGenerate('cc')" />
|
||||
<button class="btn" onclick="aiGenerate('cc')" style="white-space:nowrap;">▶ Генерировать LLM</button>
|
||||
</div>
|
||||
<div id="c-gen-prompt" style="display:none; margin-top:8px; display:none; gap:6px; align-items:center;">
|
||||
<input id="c-gen-desc" type="text"
|
||||
placeholder="Что должна делать функция? (напр: принять JSON, вернуть сумму чисел)" style="flex:1; background:#2a2a42; border:1px solid #3a3a5c; border-radius:4px;
|
||||
color:#cdd6f4; padding:6px 8px; font-size:.82rem; outline:none; width:100%;"
|
||||
onkeydown="if(event.key==='Enter')aiGenerate()" />
|
||||
<button class="btn" onclick="aiGenerate()" style="white-space:nowrap;">▶ Генерировать LLM</button>
|
||||
</div>
|
||||
<div id="c-ai-result"
|
||||
<div id="cc-ai-result"
|
||||
style="display:none;margin-top:8px;padding:10px 12px;border-radius:6px;font-size:13px;line-height:1.5;white-space:pre-wrap;font-family:monospace;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn ghost" onclick="closeCreate()">Отмена</button>
|
||||
<button id="c-submit" class="btn" onclick="submitCreate()">Создать</button>
|
||||
<button class="btn ghost" onclick="closeCreateCode()">Отмена</button>
|
||||
<button id="cc-submit" class="btn" onclick="submitCreateCode()">Создать</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Модалка: создать функцию из архива (prefix ca-) -->
|
||||
<div id="create-archive-modal" class="modal">
|
||||
<div class="panel">
|
||||
<h3>📦 Создать функцию из архива</h3>
|
||||
<div class="row">
|
||||
<div class="field">
|
||||
<label>Name</label>
|
||||
<input id="ca-name" placeholder="demo-fn">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Language</label>
|
||||
<select id="ca-lang" onchange="onLangChangeArchive()">
|
||||
<option value="python">Python</option>
|
||||
<option value="nodejs">Node.js</option>
|
||||
<option value="php">PHP</option>
|
||||
<option value="ruby">Ruby</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Entrypoint</label>
|
||||
<input id="ca-entry" value="main.main">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="field">
|
||||
<label>Route</label>
|
||||
<input id="ca-route" placeholder="/demo-fn">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Методы (через запятую)</label>
|
||||
<input id="ca-methods" value="GET">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Timeout (сек)</label>
|
||||
<input id="ca-timeout" type="number" min="1" value="60">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="field" style="min-width:240px; flex:0 0 260px;">
|
||||
<label style="display:flex; align-items:center; gap:8px; color:var(--text-primary); margin-top:18px;">
|
||||
<input id="ca-schedule-enabled" type="checkbox" onchange="toggleScheduleFields('ca')" style="width:auto;">
|
||||
Выполнять по расписанию
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Cron</label>
|
||||
<input id="ca-cron" placeholder="*/5 * * * *" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div id="ca-archive-area">
|
||||
<label>Архив (.zip)</label>
|
||||
<input type="file" id="ca-archive-file" accept=".zip" style="display:block; margin-bottom:8px; color:var(--text-primary);">
|
||||
<div style="display:flex; gap:6px; flex-wrap:wrap;">
|
||||
<button id="ca-lint-btn" class="btn ghost" onclick="lintArchiveFile('ca')">🔍 Проверка архива линтером</button>
|
||||
<button id="ca-explain-archive-btn" class="btn ghost" onclick="explainArchiveFile('ca')">📖 LLM: Что делает?</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ca-ai-result"
|
||||
style="display:none;margin-top:8px;padding:10px 12px;border-radius:6px;font-size:13px;line-height:1.5;white-space:pre-wrap;font-family:monospace;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn ghost" onclick="closeCreateArchive()">Отмена</button>
|
||||
<button id="ca-submit" class="btn" onclick="submitCreateArchive()">Создать</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -400,7 +460,7 @@
|
||||
</div>
|
||||
|
||||
<div class="actions" style="justify-content:space-between; align-items:center;">
|
||||
<span style="font-size:0.75rem; color:var(--text-secondary);">v1.3.45</span>
|
||||
<span style="font-size:0.75rem; color:var(--text-secondary);">v1.3.46</span>
|
||||
<button class="btn ghost" onclick="closeHelp()">Закрыть</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+14
-12
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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` с переключателями
|
||||
- Подключить `<script src="js/fn-code.js">` и `<script src="js/fn-archive.js">`
|
||||
- Убрать `<script>` для старой логики создания из `functions.js` (или оставить файл без этих функций)
|
||||
|
||||
### 4. Изменить `console/ui/js/functions.js` [ ]
|
||||
Удалить:
|
||||
- `openCreate()`, `closeCreate()`, `submitCreate()`
|
||||
- `setCodeMode('c', ...)` — только `c`-префикс; `e`-префикс (edit) не трогать
|
||||
- `onLangChange()` → заменяется на `onLangChangeCode()` в fn-code.js
|
||||
|
||||
---
|
||||
|
||||
## Что НЕ трогаем
|
||||
- `edit-modal` и весь edit-flow — следующий этап
|
||||
- Backend API — без изменений
|
||||
- Таблица функций — без изменений
|
||||
- `lintArchiveFile()`, `explainArchiveFile()` — остаются в `functions.js`, используются из обоих модулей по prefix
|
||||
|
||||
---
|
||||
|
||||
## Прогресс
|
||||
|
||||
| # | Задача | Статус |
|
||||
|---|--------|--------|
|
||||
| 1 | Создать `fn-code.js` | [ ] |
|
||||
| 2 | Создать `fn-archive.js` | [ ] |
|
||||
| 3 | Изменить `index.html` | [ ] |
|
||||
| 4 | Изменить `functions.js` | [ ] |
|
||||
| 5 | rsync + проверка на VM | [ ] |
|
||||
| 6 | docker build + push + apply | [ ] |
|
||||
| 7 | git commit + push | [ ] |
|
||||
|
||||
---
|
||||
|
||||
## Лог изменений
|
||||
- `js/fn-code.js` — создан: `onLangChangeCode`, `openCreateCode`, `closeCreateCode`, `submitCreateCode` (prefix `cc-`)
|
||||
- `js/fn-archive.js` — создан: `onLangChangeArchive`, `openCreateArchive`, `closeCreateArchive`, `submitCreateArchive` (prefix `ca-`)
|
||||
- `js/ai.js` — `showGenPrompt(prefix)` и `aiGenerate(prefix)` теперь принимают prefix (default `'cc'`)
|
||||
- `js/functions.js` — удалены `onLangChange`, `openCreate`, `closeCreate`, `submitCreate`
|
||||
- `index.html` — добавлены `<script>` для fn-code.js и fn-archive.js; кнопка заменена на две (✏️ Из кода / 📦 Из архива); старая `create-modal` заменена на `create-code-modal` (prefix `cc-`) и `create-archive-modal` (prefix `ca-`)
|
||||
- Версия: v1.3.45 → v1.3.46
|
||||
Reference in New Issue
Block a user