From eb517594bc62a9d4717621875057f51c89a79fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Fri, 31 Jul 2026 11:49:04 +0400 Subject: [PATCH] v1.2.4: collapsible help section in scenario editor modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scenario-form.js:
block between name and steps: - How steps work (operation → target) - Create: service + output name - Non-create: instance from scenario or cloud - Parameters: key=value format with example - Buttons: ↑↓ reorder, ✕ delete --- site/app.py | 2 +- site/static/js/scenario-form.js | 136 ++++++++++++++++---------------- 2 files changed, 69 insertions(+), 69 deletions(-) diff --git a/site/app.py b/site/app.py index 75f1193..c92df55 100644 --- a/site/app.py +++ b/site/app.py @@ -19,7 +19,7 @@ from routes.api_scenario_run import bp_run as api_scenario_run_bp from routes.api_scenario_defs import bp_defs as api_scenario_defs_bp # Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI. -VERSION = "1.2.3" +VERSION = "1.2.4" app = Flask(__name__, template_folder="templates", static_folder="static") app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc") diff --git a/site/static/js/scenario-form.js b/site/static/js/scenario-form.js index 47a3765..a30a395 100644 --- a/site/static/js/scenario-form.js +++ b/site/static/js/scenario-form.js @@ -55,6 +55,18 @@ function renderEditor() { html += '

'; html += ``; html += '
'; + // Справка + html += `
+ 📖 Как заполнять +
+ Шаг: выбери операцию → укажи цель.
+ Create: выбери сервис + задай output (имя, например d1). Другие шаги смогут ссылаться на него.
+ Остальные операции: выбери инстанс — 🆕 из create-шагов этого сценария или ☁ из облака.
+ Параметры: key = value (символические коды, как в API).
+   Пример: durationMs = 5000
+ [↑][↓] — порядок шагов, [✕] — удалить шаг. +
+
`; // Шаги html += '
Шаги
'; st.steps.forEach((s, idx) => { html += renderStepRow(idx, s); }); @@ -69,25 +81,10 @@ function renderEditor() { function renderStepRow(idx, step) { const st = scenarioEditorState; - const svcId = step.service_id; const op = step.operation; const out = step.output || ''; const ref = step.instance_ref || ''; - // Сервисы — dropdown - let svcOpts = ''; - st.services.forEach(s => { - svcOpts += ``; - }); - - // Операции — dropdown (из кеша или базовый) - let opOpts = ''; - const ops = st.opsCache[svcId] || ['create', 'delete', 'modify', 'suspend', 'resume', 'redeploy']; - ops.forEach(o => { - const oName = typeof o === 'string' ? o : o.operation; - opOpts += ``; - }); - // Output refs из предыдущих create-шагов const prevOutputs = []; for (let i = 0; i < idx; i++) { @@ -103,31 +100,37 @@ function renderStepRow(idx, step) { html += ``; html += ``; - // Сервис + Операция в одной строке - html += `
`; - html += ``; - html += ``; - html += `
`; + // 1. Операция ВСЕГДА первая + const allOps = ['create', 'delete', 'modify', 'suspend', 'resume', 'redeploy']; + let opOpts = ''; + allOps.forEach(o => { opOpts += ``; }); + html += `
`; - // Output / instance_ref / instance_uid - html += '
'; + // 2. В зависимости от операции if (op === 'create') { - html += ``; - } else if (op) { - // Предыдущие output'ы + облачные инстансы этого сервиса - let refOpts = ''; - // output'ы из предыдущих create-шагов - prevOutputs.forEach(o => { refOpts += ``; }); - // Облачные инстансы того же сервиса - const cloudInsts = (st.cloudInstances || []).filter(i => i.serviceId == svcId && i.explainedStatus !== 'deleted'); - if (cloudInsts.length) refOpts += ''; - cloudInsts.forEach(i => { - const sel = i.instanceUid == ref ? 'selected' : ''; - refOpts += ``; + // Сервис + let svcOpts = ''; + st.services.forEach(s => { + svcOpts += ``; }); - html += ``; + html += `
`; + html += `
`; + } else if (op) { + // Инстанс — облачные + предыдущие output'ы + let refOpts = ''; + // output'ы из create-шагов + prevOutputs.forEach(o => { refOpts += ``; }); + // Облачные инстансы + if (st.cloudInstances && st.cloudInstances.length) { + refOpts += ''; + st.cloudInstances.forEach(i => { + if (i.explainedStatus === 'deleted') return; + const sel = i.instanceUid == ref ? 'selected' : ''; + refOpts += ``; + }); + } + html += `
`; } - html += '
'; // Параметры html += `
`; @@ -145,24 +148,7 @@ function renderStepRow(idx, step) { return html; } -async function onSvcChange(idx) { - const svcEl = document.getElementById('step-' + idx + '-svc'); - const svcId = parseInt(svcEl?.value) || 0; - if (!svcId) return; - // Загрузить операции для сервиса - try { - const r = await fetch('/api/operations/' + svcId); - const d = await r.json(); - scenarioEditorState.opsCache[svcId] = d.operations || []; - } catch (e) { - scenarioEditorState.opsCache[svcId] = ['create', 'delete', 'modify', 'suspend', 'resume', 'redeploy']; - } - renderEditor(); -} - -async function onOpChange(idx) { - // Ничего не делаем — параметры редактируются вручную -} +function onOpChange(idx) { renderEditor(); } function addStep() { scenarioEditorState.steps.push({ service_id: '', operation: '', output: '', instance_ref: '', instance_uid: '', params: [] }); @@ -194,26 +180,40 @@ function collectFormSteps() { const name = (document.getElementById('scenario-name')?.value || '').trim(); const steps = []; st.steps.forEach((_, idx) => { - const svcEl = document.getElementById('step-' + idx + '-svc'); const opEl = document.getElementById('step-' + idx + '-op'); - const outEl = document.getElementById('step-' + idx + '-output'); - const refEl = document.getElementById('step-' + idx + '-ref'); - const uidEl = document.getElementById('step-' + idx + '-uid'); - const svcId = parseInt(svcEl?.value) || 0; const operation = (opEl?.value || '').trim(); - const output = (outEl?.value || '').trim(); - const instance_ref = (refEl?.value || '').trim(); - const instance_uid = (uidEl?.value || '').trim(); - const params = {}; + let svcId = 0; + const s = { operation, params: {} }; + + if (operation === 'create') { + const svcEl = document.getElementById('step-' + idx + '-svc'); + svcId = parseInt(svcEl?.value) || 0; + const outEl = document.getElementById('step-' + idx + '-output'); + const output = (outEl?.value || '').trim(); + if (output) s.output = output; + } else { + const refEl = document.getElementById('step-' + idx + '-ref'); + const ref = (refEl?.value || '').trim(); + if (ref) { + // Это UUID облачного инстанса — ищем его service_id + const cloud = (st.cloudInstances || []).find(i => i.instanceUid === ref); + if (cloud) { + svcId = cloud.serviceId; + s.instance_uid = ref; + } else { + // Это output-имя из предыдущего шага + s.instance_ref = ref; + } + } + } + s.service_id = svcId; + + // Параметры st.steps[idx].params.forEach((__, pi) => { const k = document.getElementById('step-' + idx + '-pk-' + pi)?.value?.trim(); const v = document.getElementById('step-' + idx + '-pv-' + pi)?.value || ''; - if (k) params[k] = v; + if (k) s.params[k] = v; }); - const s = { service_id: svcId, operation, params }; - if (output) s.output = output; - if (instance_ref) s.instance_ref = instance_ref; - if (instance_uid) s.instance_uid = instance_uid; steps.push(s); }); return { name, steps };