// scenario-form.js — модальный редактор сценариев // // Это САМЫЙ СЛОЖНЫЙ модуль фронтенда (~358 строк). // Редактор позволяет создавать/редактировать сценарии через модальное окно. // // СОСТОЯНИЕ РЕДАКТОРА: scenarioEditorState = { // defId, version, name, steps: [{service_id, operation, output, instance_ref, instance_uid, params, _svcOpId}], // services: [], cloudInstances: [], opsCache: {}, paramDefsCache: {} // } // // ЖИЗНЕННЫЙ ЦИКЛ РЕДАКТИРОВАНИЯ: // 1. showScenarioEditor(def) — инициализация, загрузка сервисов/инстансов, renderEditor() // 2. renderEditor() — отрисовка модала с шагами // 3. Пользователь меняет операцию → onOpChange → renderEditor (перерисовка) // 4. Для каждого шага с операцией — loadStepParams (GET /api/params/{opId}) // 5. Параметры рендерятся через params-render.js // 6. Перед перерисовкой — snapshotAllParams (сохранить несохранённые правки) // 7. Сохранение — collectFormSteps → POST/PUT /api/scenario/definitions // // ОБРАТНЫЙ МАППИНГ ПАРАМЕТРОВ: // В форме параметры хранятся с числовыми ID (как и в API). // В БД сценария — с символьными именами. // При сохранении: numeric_id → name (через paramDefsCache) // При загрузке: name → numeric_id (подстановка в defaultValue) let scenarioEditorState = null; /** * Открыть редактор сценария. * * @param {Object|null} def — определение сценария или null (новый) */ async function showScenarioEditor(def) { // Инициализация состояния scenarioEditorState = def ? { defId: def.id, version: def.version, name: def.name, steps: (def.steps || []).map(s => ({ service_id: s.service_id || '', operation: s.operation || '', output: s.output || '', instance_ref: s.instance_ref || '', instance_uid: s.instance_uid || '', _legacyInstanceFallback: s.operation !== 'create' && !s.instance_ref && !s.instance_uid, _showCloud: Boolean(s.instance_uid), params: s.params || {} // {name: value} — символьные имена })), services: [], cloudInstances: [], opsCache: {}, paramDefsCache: {} } : { defId: null, version: 1, name: '', steps: [], services: [], cloudInstances: [], opsCache: {}, paramDefsCache: {} }; // Загрузить справочные данные: сервисы + инстансы (для select'ов) try { const [svcR, instR] = await Promise.all([ fetch('/api/services').then(r => r.json()), fetch('/api/instances/list').then(r => r.json()) ]); scenarioEditorState.services = svcR || []; scenarioEditorState.cloudInstances = instR || []; } catch (e) { scenarioEditorState.services = []; scenarioEditorState.cloudInstances = []; } const initialServiceIds = [...new Set(scenarioEditorState.steps .map(step => parseInt(step.service_id, 10)) .filter(serviceId => serviceId > 0))]; await Promise.all(initialServiceIds.map(loadServiceOperations)); renderEditor(); } /** * Открыть модальное окно с HTML-контентом. * * @param {string} html — HTML-контент модала */ function openModal(html) { let modal = document.getElementById('scenario-modal'); if (!modal) { // Создать модал при первом вызове modal = document.createElement('div'); modal.id = 'scenario-modal'; // backdrop — закрытие по клику вне модала modal.innerHTML = '
'; document.body.appendChild(modal); } modal.style.display = 'block'; document.getElementById('modal-content').innerHTML = html; } /** * Закрыть модальное окно и обновить список сценариев. */ function closeModal() { const modal = document.getElementById('scenario-modal'); if (modal) modal.style.display = 'none'; loadScenarios(); // обновить список (могли измениться) } // ═══════════════════════════════════════════════════════ // Снапшот параметров — защита от потери правок // ═══════════════════════════════════════════════════════ /** * Сохранить параметры ОДНОГО шага перед перерисовкой. * * Зачем: renderEditor() пересоздаёт ВЕСЬ HTML → все несохранённые правки теряются. * Эта функция собирает текущие значения из DOM и сохраняет в step.params. * * @param {number} idx — индекс шага */ function snapshotStepParams(idx) { const st = scenarioEditorState; const container = document.getElementById('step-' + idx + '-params'); if (!container || !container.innerHTML) return; // Собрать значения из формы (числовые ID → значение) const numericParams = collectParams('#step-' + idx + '-params'); const defs = st.paramDefsCache[st.steps[idx]._svcOpId]; if (!defs) return; // Обратный маппинг: numeric_id → name const idToName = {}; defs.forEach(p => { idToName[p.svcOperationCfsParamId] = p.name; }); const result = {}; for (const [id, val] of Object.entries(numericParams)) { const name = idToName[parseInt(id)] || id; // Сохраняем только НЕ-дефолтные значения if (val !== '' && val !== '{}' && val !== '[]' && val !== 'false') { result[name] = val; } } st.steps[idx].params = result; } /** * Снапшот ВСЕХ шагов — вызывается перед ЛЮБОЙ перерисовкой. */ function snapshotAllParams() { if (!scenarioEditorState) return; scenarioEditorState.steps.forEach((_, idx) => snapshotStepParams(idx)); } // ═══════════════════════════════════════════════════════ // Резолв операции + загрузка параметров // ═══════════════════════════════════════════════════════ /** * Загрузить доступные операции сервиса и сохранить их в кеше редактора. * * @param {number|string} svcId — ID сервиса * @returns {Promised1).