Files
app-autotest/site/static/js/scenario-form.js
T
naeel a96658b9b5 v1.1.55: split large files + CRUD scenario editor
Backend (5→7 files):
- api_test.py: 622→479, terraform functions → operations/terraform.py (142)
- api_scenario.py: 241→split into run (153) + defs (108) with _validate_steps()
- db/scenario_defs.py: +client_id, stand, is_seed in list_definitions

Frontend (1→10 files):
- app.js: 554→16 (loader), split into:
  utils.js (45), instances.js (89), operations.js (249),
  history.js (35), scenario-list.js (90)
- New CRUD: scenario-form.js (128), create (25), edit (12), delete (13)

Max file size: JS 249, PY 479
2026-07-30 22:02:22 +04:00

129 lines
6.2 KiB
JavaScript

// scenario-form.js — общий редактор шагов сценария (create + edit)
let scenarioEditorState = null; // {defId, version, name, steps:[]}
// steps: [{service_id, operation, params: [["key","val"],...]}]
function showScenarioEditor(def) {
const body = document.getElementById('scenario-body');
scenarioEditorState = def ? {
defId: def.id, version: def.version, name: def.name,
steps: (def.steps || []).map(s => ({
service_id: s.service_id, operation: s.operation,
params: Object.entries(s.params || {})
}))
} : { defId: null, version: 1, name: '', steps: [] };
renderEditor();
}
function renderEditor() {
const st = scenarioEditorState;
let html = '<div style="font-size:12px;">';
html += '<div style="margin-bottom:6px;"><label style="font-size:11px;">Название:</label><br>';
html += `<input type="text" id="scenario-name" value="${_esc(st.name)}" style="width:100%;padding:4px;" placeholder="my_test">`;
html += '</div>';
html += '<div style="margin-bottom:6px;font-size:11px;font-weight:600;">Шаги:</div>';
st.steps.forEach((s, idx) => {
html += renderStepRow(idx, s);
});
html += `<button class="btn btn-sm" style="font-size:11px;margin-top:4px;" onclick="addStep()">+ Добавить шаг</button>`;
html += '<div style="margin-top:8px;">';
html += `<button class="btn btn-sm" style="font-size:11px;background:var(--brand-primary);color:#fff;" onclick="saveScenario()">Сохранить</button> `;
html += '<button class="btn btn-sm" style="font-size:11px;" onclick="loadScenarios()">Отмена</button>';
html += '</div></div>';
body.innerHTML = html;
}
function renderStepRow(idx, step) {
const svcId = step.service_id || '';
const op = step.operation || '';
const params = step.params || [];
let html = `<div style="border:1px solid var(--brand-gray);border-radius:4px;padding:6px;margin:4px 0;" id="step-${idx}">`;
html += `<div style="font-size:10px;color:var(--muted);margin-bottom:4px;">Шаг ${idx+1}`;
html += ` <button class="btn btn-sm" style="font-size:9px;padding:0 4px;" onclick="removeStep(${idx})">✕</button>`;
html += `</div>`;
// Сервис — текстовое поле с подсказкой (ID или имя)
html += `<input type="number" id="step-${idx}-svc" value="${svcId}" placeholder="service_id" style="width:80px;padding:2px;font-size:11px;" onchange="onStepChange(${idx})"> `;
// Операция
html += `<input type="text" id="step-${idx}-op" value="${_esc(op)}" placeholder="create" style="width:100px;padding:2px;font-size:11px;"> `;
html += '<div style="margin-top:2px;">';
params.forEach(([k, v], pi) => {
html += `<div style="display:flex;gap:4px;align-items:center;margin:2px 0;">`;
html += `<input type="text" id="step-${idx}-pk-${pi}" value="${_esc(k)}" placeholder="param" style="width:100px;padding:2px;font-size:10px;">`;
html += `<span>=</span>`;
html += `<input type="text" id="step-${idx}-pv-${pi}" value="${_esc(v)}" placeholder="value" style="width:120px;padding:2px;font-size:10px;">`;
html += `<button class="btn btn-sm" style="font-size:9px;padding:0 3px;" onclick="removeParam(${idx},${pi});renderEditor();">✕</button>`;
html += `</div>`;
});
html += `<button class="btn btn-sm" style="font-size:9px;margin-top:2px;" onclick="addParam(${idx});renderEditor();">+ Параметр</button>`;
html += '</div></div>';
return html;
}
function addStep() {
scenarioEditorState.steps.push({ service_id: 1, operation: 'create', params: [] });
renderEditor();
}
function removeStep(idx) {
scenarioEditorState.steps.splice(idx, 1);
renderEditor();
}
function addParam(idx) {
scenarioEditorState.steps[idx].params.push(['', '']);
}
function removeParam(idx, pi) {
scenarioEditorState.steps[idx].params.splice(pi, 1);
}
function onStepChange(idx) { /* placeholder for future auto-load */ }
function collectFormSteps() {
const st = scenarioEditorState;
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 svcId = parseInt(svcEl?.value) || 0;
const operation = (opEl?.value || '').trim();
const params = {};
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;
});
steps.push({ service_id: svcId, operation, params });
});
return { name, steps };
}
async function saveScenario() {
const { name, steps } = collectFormSteps();
if (!name) { alert('Введите название'); return; }
if (!steps.length) { alert('Добавьте хотя бы один шаг'); return; }
for (let i = 0; i < steps.length; i++) {
if (!steps[i].service_id) { alert('Шаг ' + (i+1) + ': укажите service_id'); return; }
if (!steps[i].operation) { alert('Шаг ' + (i+1) + ': укажите operation'); return; }
}
const st = scenarioEditorState;
const isNew = !st.defId;
const url = isNew ? '/api/scenario/definitions' : '/api/scenario/definitions/' + st.defId;
const method = isNew ? 'POST' : 'PUT';
const body = isNew ? { name, steps } : { name, steps, version: st.version };
try {
const r = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
const d = await r.json();
if (r.status === 409) {
alert('⚠️ Конфликт версий — кто-то уже изменил сценарий. Обновите страницу.');
return;
}
if (d.error) { alert(d.error); return; }
if (isNew && d.id) st.defId = d.id;
if (d.version) st.version = d.version;
await loadScenarios();
} catch (e) {
alert('Ошибка сохранения: ' + e.message);
}
}