v1.2.3: instance_ref shows cloud instances + bigger CRUD buttons

scenario-form.js:
- showScenarioEditor loads /api/instances/list alongside services
- renderStepRow: non-create instance_ref dropdown shows:
  🆕 outputs from previous create steps
  ☁ existing cloud instances filtered by service_id
- Removed separate instance_uid text field (now in dropdown)

scenario-list.js:
- CRUD buttons: ✏📋🗑 bigger (13px, padding), vertical layout
- Run button: full-width, left-aligned
This commit is contained in:
2026-07-31 11:40:29 +04:00
parent 15f5ebe647
commit 622a7c7023
3 changed files with 29 additions and 17 deletions
+1 -1
View File
@@ -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 from routes.api_scenario_defs import bp_defs as api_scenario_defs_bp
# Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI. # Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI.
VERSION = "1.2.2" VERSION = "1.2.3"
app = Flask(__name__, template_folder="templates", static_folder="static") 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") app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc")
+20 -8
View File
@@ -17,11 +17,15 @@ async function showScenarioEditor(def) {
services: [], opsCache: {} services: [], opsCache: {}
} : { defId: null, version: 1, name: '', steps: [], services: [], opsCache: {} }; } : { defId: null, version: 1, name: '', steps: [], services: [], opsCache: {} };
// Загрузить список сервисов // Загрузить список сервисов и облачные инстансы
try { try {
const r = await fetch('/api/services'); const [svcR, instR] = await Promise.all([
scenarioEditorState.services = await r.json(); fetch('/api/services').then(r => r.json()),
} catch (e) { scenarioEditorState.services = []; } fetch('/api/instances/list').then(r => r.json())
]);
scenarioEditorState.services = svcR || [];
scenarioEditorState.cloudInstances = instR || [];
} catch (e) { scenarioEditorState.services = []; scenarioEditorState.cloudInstances = []; }
renderEditor(); renderEditor();
} }
@@ -110,10 +114,18 @@ function renderStepRow(idx, step) {
if (op === 'create') { if (op === 'create') {
html += `<input type="text" id="step-${idx}-output" value="${_esc(out)}" placeholder="output (имя для ссылок)" style="width:180px;padding:3px;font-size:11px;">`; html += `<input type="text" id="step-${idx}-output" value="${_esc(out)}" placeholder="output (имя для ссылок)" style="width:180px;padding:3px;font-size:11px;">`;
} else if (op) { } else if (op) {
let refOpts = '<option value="">— instance_ref —</option>'; // Предыдущие output'ы + облачные инстансы этого сервиса
prevOutputs.forEach(o => { refOpts += `<option value="${o}" ${o == ref ? 'selected' : ''}>${_esc(o)}</option>`; }); let refOpts = '<option value="">— инстанс —</option>';
html += `<select id="step-${idx}-ref" style="padding:3px;font-size:11px;">${refOpts}</select>`; // output'ы из предыдущих create-шагов
html += `<input type="text" id="step-${idx}-uid" value="${_esc(step.instance_uid || '')}" placeholder="или явный UUID" style="width:280px;padding:3px;font-size:11px;">`; prevOutputs.forEach(o => { refOpts += `<option value="${o}" ${o == ref ? 'selected' : ''}>🆕 ${_esc(o)}</option>`; });
// Облачные инстансы того же сервиса
const cloudInsts = (st.cloudInstances || []).filter(i => i.serviceId == svcId && i.explainedStatus !== 'deleted');
if (cloudInsts.length) refOpts += '<option disabled>── облако ──</option>';
cloudInsts.forEach(i => {
const sel = i.instanceUid == ref ? 'selected' : '';
refOpts += `<option value="${i.instanceUid}" ${sel}>☁ ${_esc(i.displayName)} (${_esc(i.explainedStatus || '?')})</option>`;
});
html += `<select id="step-${idx}-ref" style="padding:3px;font-size:11px;max-width:400px;">${refOpts}</select>`;
} }
html += '</div>'; html += '</div>';
+8 -8
View File
@@ -18,17 +18,17 @@ async function loadScenarios(){
fetch('/api/scenario/status').then(r=>r.json()), fetch('/api/scenario/status').then(r=>r.json()),
]); ]);
let html=''; let html='';
html+=`<button class="btn btn-sm" style="font-size:10px;margin-bottom:4px;" onclick="createScenario()">+ Создать сценарий</button>`; html+=`<button class="btn btn-sm" style="font-size:11px;margin-bottom:4px;" onclick="createScenario()">+ Создать сценарий</button>`;
if(sr.length){ if(sr.length){
html+='<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:4px;">'; html+='<div style="display:flex;flex-direction:column;gap:4px;margin-bottom:4px;">';
sr.forEach(s=>{ sr.forEach(s=>{
const seed = s.is_seed; const seed = s.is_seed;
html+=`<div style="display:inline-flex;gap:2px;align-items:center;">`; html+=`<div style="display:flex;align-items:center;gap:4px;">`;
html+=`<button class="btn btn-sm" style="font-size:11px;" onclick="runScenario(${s.id})" title="Запустить">▶ ${_esc(s.name)} <span style="color:var(--muted);">(${s.steps} шагов)</span></button>`; html+=`<button class="btn btn-sm" style="font-size:12px;flex:1;text-align:left;" onclick="runScenario(${s.id})" title="Запустить">▶ ${_esc(s.name)} <span style="color:var(--muted);">(${s.steps} шагов)</span></button>`;
if(!seed) html+=`<button class="btn btn-sm" style="font-size:9px;padding:0 4px;" onclick="editScenario(${s.id})" title="Редактировать">✏</button>`; if(!seed) html+=`<button class="btn btn-sm" style="font-size:13px;padding:0 6px;" onclick="editScenario(${s.id})" title="Редактировать">✏</button>`;
html+=`<button class="btn btn-sm" style="font-size:9px;padding:0 4px;" onclick="cloneScenario(${s.id},'${_esc(s.name)}')" title="Клонировать"></button>`; html+=`<button class="btn btn-sm" style="font-size:13px;padding:0 6px;" onclick="cloneScenario(${s.id},'${_esc(s.name)}')" title="Копировать">📋</button>`;
if(!seed) html+=`<button class="btn btn-sm" style="font-size:9px;padding:0 4px;color:var(--destructive);" onclick="deleteScenario(${s.id},'${_esc(s.name)}')" title="Удалить">🗑</button>`; if(!seed) html+=`<button class="btn btn-sm" style="font-size:13px;padding:0 6px;color:var(--destructive);" onclick="deleteScenario(${s.id},'${_esc(s.name)}')" title="Удалить">🗑</button>`;
if(seed) html+=`<span style="font-size:8px;color:var(--muted);">seed</span>`; if(seed) html+=`<span style="font-size:9px;color:var(--muted);padding:0 4px;">seed</span>`;
html+=`</div>`; html+=`</div>`;
}); });
html+='</div>'; html+='</div>';