fix: adapt autotest to updated service APIs

This commit is contained in:
2026-10-25 16:30:25 +03:00
parent 8d956c8063
commit 4203e5674c
13 changed files with 462 additions and 292 deletions
+71 -70
View File
@@ -16,12 +16,12 @@
// busy — флаг блокировки UI (пока идёт операция)
// AUTOTEST_PREFIX — "autotest-" (фильтр "наших" инстансов)
let svcInstances=[], selectedInst=null, selectedOp=null;
let currentSvcId=(window.APP&&window.APP.firstServiceId)||1;
let currentSvcName='';
let currentSvcShort='';
let busy=false;
const AUTOTEST_PREFIX='autotest-';
let svcInstances = [], selectedInst = null, selectedOp = null;
let currentSvcId = (window.APP && window.APP.firstServiceId) || 1;
let currentSvcName = '';
let currentSvcShort = '';
let busy = false;
const AUTOTEST_PREFIX = 'autotest-';
/**
* Выбрать сервис — загрузить его инстансы и показать в списке.
@@ -29,37 +29,37 @@ const AUTOTEST_PREFIX='autotest-';
*
* @param {number} svcId — ID сервиса
*/
async function selectService(svcId){
if(busy) return; // блокировка — идёт операция
async function selectService(svcId) {
if (busy) return; // блокировка — идёт операция
// Сброс предыдущего состояния
currentSvcId=svcId;
selectedInst=null;
selectedOp=null;
currentSvcId = svcId;
selectedInst = null;
selectedOp = null;
stopPoll(); // остановить поллинг предыдущей операции
// Скрыть панель параметров и этапы
document.getElementById('params-area').style.display='none';
document.querySelectorAll('.stages-box').forEach(e=>e.remove());
document.getElementById('params-area').style.display = 'none';
document.querySelectorAll('.stages-box').forEach(e => e.remove());
// Подсветка активного сервиса в списке
document.querySelectorAll('.svc-item').forEach(e=>e.classList.remove('active'));
const svcEl=document.querySelector(`.svc-item[onclick*="${svcId}"]`);
if(svcEl) svcEl.classList.add('active');
document.querySelectorAll('.svc-item').forEach(e => e.classList.remove('active'));
const svcEl = document.querySelector(`.svc-item[onclick*="${svcId}"]`);
if (svcEl) svcEl.classList.add('active');
try{
try {
// GET /api/operations/{svcId} — возвращает операции + autotest-инстансы
const r=await fetch('/api/operations/'+svcId);
const d=await r.json();
svcInstances=d.instances||[];
currentSvcName=d.svc||'';
currentSvcShort=d.svcShort||'';
const r = await fetch('/api/operations/' + svcId);
const d = await r.json();
svcInstances = d.instances || [];
currentSvcName = d.svc || '';
currentSvcShort = d.svcShort || '';
// Обновить текст кнопки «Создать»
const btnSpan=document.querySelector('#create-btn-area span');
if(btnSpan) btnSpan.textContent='+ Создать новый инстанс '+(currentSvcName||'сервиса');
const btnSpan = document.querySelector('#create-btn-area span');
if (btnSpan) btnSpan.textContent = '+ Создать новый инстанс ' + (currentSvcName || 'сервиса');
renderInstances(); // отрисовать список
}catch(e){
document.getElementById('inst-list').innerHTML=
} catch (e) {
document.getElementById('inst-list').innerHTML =
'<span style="color:var(--destructive);font-size:11px;">Ошибка загрузки</span>';
}
}
@@ -68,37 +68,38 @@ async function selectService(svcId){
* Отрисовать список инстансов в #inst-list.
* Каждый инстанс — строка: имя + сервис + статус + created + modified.
*/
function renderInstances(){
let html='';
svcInstances.forEach(i=>{
function renderInstances() {
let html = '';
svcInstances.forEach(i => {
// Статус: из нашего поля status (cloud-first) или explainedStatus
const status=i.status||i.explainedStatus||'?';
const sc=status==='running'?'badge-success':'';
const status = i.status || i.explainedStatus || '?';
const sc = (status === 'running' || status === 'Выполнен') ? 'badge-success' : '';
const created = i.instanceConfigDtCreated || i.dtCreate || '';
// Строка инстанса — кликабельна
html+=`<div class="inst-item" onclick="toggleInstance('${i.instanceUid}')" data-iuid="${i.instanceUid}">
html += `<div class="inst-item" onclick="toggleInstance('${i.instanceUid}')" data-iuid="${i.instanceUid}">
<span style="display:inline-block;width:220px;">${_esc(i.displayName)}</span>
<span style="display:inline-block;width:140px;color:var(--muted);font-size:11px;">${_esc(i.svc||'')}</span>
<span style="display:inline-block;width:140px;color:var(--muted);font-size:11px;">${_esc(i.svc || '')}</span>
<span class="badge ${sc}" style="font-size:9px;">${_esc(status)}</span>
<span style="display:inline-block;width:60px;color:var(--muted);font-size:9px;text-align:right;" title="${i.instanceConfigDtCreated||''}">${relativeTime(i.instanceConfigDtCreated)}</span>
<span style="display:inline-block;width:60px;color:var(--muted);font-size:9px;text-align:right;" title="${i.instanceConfigDtModified||''}">${relativeTime(i.instanceConfigDtModified)}</span>
<span style="display:inline-block;width:60px;color:var(--muted);font-size:9px;text-align:right;" title="${created}">${relativeTime(created)}</span>
<span style="display:inline-block;width:60px;color:var(--muted);font-size:9px;text-align:right;" title="${i.instanceConfigDtModified || ''}">${relativeTime(i.instanceConfigDtModified)}</span>
</div>`;
// Контейнер для кнопок операций (изначально скрыт)
html+=`<div class="inst-ops" id="ops-${i.instanceUid}"></div>`;
html += `<div class="inst-ops" id="ops-${i.instanceUid}"></div>`;
});
document.getElementById('inst-list').innerHTML=html;
document.getElementById('inst-list').innerHTML = html;
}
/**
* Обновить список инстансов (без смены сервиса).
* Вызывается после завершения операции.
*/
async function refreshInstances(){
try{
const r=await fetch('/api/operations/'+currentSvcId);
const d=await r.json();
svcInstances=d.instances||[];
async function refreshInstances() {
try {
const r = await fetch('/api/operations/' + currentSvcId);
const d = await r.json();
svcInstances = d.instances || [];
renderInstances();
}catch(e){}
} catch (e) { }
}
/**
@@ -107,52 +108,52 @@ async function refreshInstances(){
*
* @param {string} iuid — instanceUid
*/
async function toggleInstance(iuid){
if(busy) return;
selectedInst=iuid;
async function toggleInstance(iuid) {
if (busy) return;
selectedInst = iuid;
stopPoll();
// Подсветка выбранного инстанса
document.querySelectorAll('[data-iuid]').forEach(e=>
e.classList.toggle('active',e.dataset.iuid===iuid));
document.querySelectorAll('[data-iuid]').forEach(e =>
e.classList.toggle('active', e.dataset.iuid === iuid));
// Скрыть панель параметров (если была открыта для другого инстанса)
document.getElementById('params-area').style.display='none';
document.getElementById('params-area').style.display = 'none';
const opsEl=document.getElementById('ops-'+iuid);
const opsEl = document.getElementById('ops-' + iuid);
// Если уже открыт — закрыть (toggle)
if(opsEl.classList.contains('open')){opsEl.classList.remove('open');return;}
if (opsEl.classList.contains('open')) { opsEl.classList.remove('open'); return; }
// Закрыть ВСЕ другие открытые панели операций
document.querySelectorAll('.inst-ops').forEach(e=>e.classList.remove('open'));
document.querySelectorAll('.inst-ops').forEach(e => e.classList.remove('open'));
// Найти инстанс в списке (может быть из другого сервиса)
const inst=svcInstances.find(x=>x.instanceUid===iuid);
const svcId=inst?inst.serviceId:currentSvcId;
const inst = svcInstances.find(x => x.instanceUid === iuid);
const svcId = inst ? inst.serviceId : currentSvcId;
try{
const r=await fetch('/api/operations/'+svcId);
const d=await r.json();
try {
const r = await fetch('/api/operations/' + svcId);
const d = await r.json();
// Фильтруем операции: убираем reconcile (техническая) и create (уже создан)
let ops=(d.operations||[]).filter(o=>o.operation!=='reconcile'&&o.operation!=='create');
let ops = (d.operations || []).filter(o => o.operation !== 'reconcile' && o.operation !== 'create');
// Для not created инстансов — только delete
if(inst&&(inst.status||inst.explainedStatus)==='not created'){
ops=ops.filter(o=>o.operation==='delete');
if (inst && (inst.status || inst.explainedStatus) === 'not created') {
ops = ops.filter(o => o.operation === 'delete');
}
// Кнопки операций с цветовой маркировкой
// o.operation в onclick — JS-escape: \ → \\, ' → \'
// o.operation в тексте кнопки — HTML-escape через _esc()
opsEl.innerHTML=ops.map(o=>{
opsEl.innerHTML = ops.map(o => {
const escOp = _esc(o.operation);
const jsOp = o.operation.replace(/\\/g,'\\\\').replace(/'/g,"\\'");
const jsOp = o.operation.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
return `<button class="btn btn-sm op-btn ${opClass(o.operation)}" onclick="runOp('${jsOp}',${o.svcOperationId})">${escOp}</button>`;
}).join('');
opsEl.classList.add('open');
}catch(e){
opsEl.innerHTML='<span style="color:var(--destructive);font-size:11px;">Ошибка загрузки</span>';
} catch (e) {
opsEl.innerHTML = '<span style="color:var(--destructive);font-size:11px;">Ошибка загрузки</span>';
opsEl.classList.add('open');
}
}
@@ -163,12 +164,12 @@ async function toggleInstance(iuid){
* @param {string} opName — "modify"/"delete"/"suspend"/...
* @returns {string} CSS-класс
*/
function opClass(opName){
if(opName==='modify') return 'op-btn-modify'; // синий
if(opName==='suspend') return 'op-btn-suspend'; // оранжевый
if(opName==='resume') return 'op-btn-resume'; // зелёный
if(opName==='delete') return 'op-btn-delete'; // красный
if(opName==='redeploy') return 'op-btn-redeploy'; // фиолетовый
function opClass(opName) {
if (opName === 'modify') return 'op-btn-modify'; // синий
if (opName === 'suspend') return 'op-btn-suspend'; // оранжевый
if (opName === 'resume') return 'op-btn-resume'; // зелёный
if (opName === 'delete') return 'op-btn-delete'; // красный
if (opName === 'redeploy') return 'op-btn-redeploy'; // фиолетовый
return '';
}