UI: instances + expand operations, create separate, only dummy, v1.0.40
This commit is contained in:
+1
-1
@@ -6,7 +6,7 @@ from routes.main import bp as main_bp
|
||||
from routes.api import bp as api_bp
|
||||
from routes.api_test import bp as api_test_bp
|
||||
|
||||
VERSION = "1.0.39"
|
||||
VERSION = "1.0.40"
|
||||
|
||||
app = Flask(__name__, template_folder="templates", static_folder="static")
|
||||
app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc")
|
||||
|
||||
+65
-61
@@ -13,8 +13,10 @@
|
||||
.col-main { flex:1; min-width:0; }
|
||||
.svc-item { cursor:pointer; padding:4px 8px; font-size:12px; border-radius:4px; }
|
||||
.svc-item:hover, .svc-item.active { background:var(--brand-grey-light); }
|
||||
.op-item { display:flex; align-items:center; gap:6px; padding:3px 8px; font-size:12px; }
|
||||
.op-item.disabled { opacity:0.4; }
|
||||
.inst-item { cursor:pointer; display:flex; align-items:center; gap:6px; padding:4px 8px; font-size:12px; border-radius:4px; }
|
||||
.inst-item:hover, .inst-item.active { background:var(--brand-grey-light); }
|
||||
.inst-ops { display:none; margin-left:16px; }
|
||||
.inst-ops.open { display:block; }
|
||||
.btn-sm { height:24px; font-size:11px; padding:0 8px; }
|
||||
.param-row { display:flex; align-items:center; gap:8px; margin-bottom:6px; }
|
||||
.param-row label { width:160px; font-size:11px; text-align:right; flex-shrink:0; }
|
||||
@@ -68,17 +70,19 @@
|
||||
<div class="col-svc">
|
||||
<div class="card">
|
||||
<div class="card-header" style="font-size:12px;">Сервисы</div>
|
||||
<div class="card-body" style="padding:4px;max-height:70vh;overflow-y:auto;" id="svc-list">
|
||||
<span style="color:var(--muted);font-size:11px;">Загрузка...</span>
|
||||
<div class="card-body" style="padding:4px;">
|
||||
<div class="svc-item active" onclick="selectService(1)">1. Болванка</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Операции + Параметры + Результат -->
|
||||
<!-- Инстансы + Операции + Параметры -->
|
||||
<div class="col-main">
|
||||
<div class="card" id="ops-card" style="display:none;">
|
||||
<div class="card-header" style="font-size:12px;" id="ops-title">Операции</div>
|
||||
<div class="card-body" style="padding:4px;" id="ops-list"></div>
|
||||
<div class="card">
|
||||
<div class="card-header" style="font-size:12px;">Инстансы</div>
|
||||
<div class="card-body" style="padding:4px;" id="inst-list">
|
||||
<span style="color:var(--muted);font-size:11px;">Загрузка...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" id="params-card" style="margin-top:8px;display:none;">
|
||||
<div class="card-header" style="font-size:12px;">Параметры</div>
|
||||
@@ -92,21 +96,14 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let selectedSvc=null, selectedOp=null, svcInstances=[], selectedInst=null;
|
||||
let svcInstances=[], selectedInst=null, selectedOp=null;
|
||||
const SVC_ID=1;
|
||||
|
||||
(async function(){
|
||||
const el=document.getElementById('svc-list');
|
||||
try{
|
||||
const r=await fetch('/api/services');
|
||||
const data=await r.json();
|
||||
el.innerHTML=data.map(s=>`<div class="svc-item" data-svc-id="${s.svcId}" onclick="selectService(${s.svcId})">${s.svcId}. ${s.svc}</div>`).join('');
|
||||
if(data.length>0) selectService(data[0].svcId);
|
||||
}catch(e){el.innerHTML='<span style="color:var(--destructive);font-size:11px;">Ошибка загрузки</span>';}
|
||||
})();
|
||||
// Загрузка при старте
|
||||
selectService(SVC_ID);
|
||||
|
||||
async function selectService(svcId){
|
||||
selectedSvc=svcId; selectedOp=null; selectedInst=null;
|
||||
document.querySelectorAll('.svc-item').forEach(e=>e.classList.toggle('active',e.dataset.svcId==String(svcId)));
|
||||
selectedInst=null; selectedOp=null;
|
||||
document.getElementById('params-card').style.display='none';
|
||||
document.getElementById('btn-test').style.display='none';
|
||||
|
||||
@@ -114,46 +111,60 @@ async function selectService(svcId){
|
||||
const d=await r.json();
|
||||
svcInstances=d.instances||[];
|
||||
|
||||
document.getElementById('ops-card').style.display='block';
|
||||
document.getElementById('ops-title').textContent=d.svc+' — операции';
|
||||
|
||||
let html='';
|
||||
// Инстансы с именами и статусами
|
||||
if(svcInstances.length>0){
|
||||
html+='<div style="font-size:11px;font-weight:600;padding:4px 8px;color:var(--muted);">Инстансы</div>';
|
||||
svcInstances.forEach(i=>{
|
||||
const sc=i.explainedStatus==='running'?'badge-success':'';
|
||||
html+=`<div class="op-item" style="cursor:pointer;" onclick="selectInstance('${i.instanceUid}')" data-iuid="${i.instanceUid}">
|
||||
<span style="flex:1;font-size:12px;">${i.displayName}</span>
|
||||
html+=`<div class="inst-item" onclick="toggleInstance('${i.instanceUid}')" data-iuid="${i.instanceUid}">
|
||||
<span style="flex:1;">${i.displayName}</span>
|
||||
<span class="badge ${sc}" style="font-size:9px;">${i.explainedStatus||'?'}</span>
|
||||
</div>`;
|
||||
html+=`<div class="inst-ops" id="ops-${i.instanceUid}"></div>`;
|
||||
});
|
||||
// Кнопка создать
|
||||
html+=`<div class="inst-item" style="margin-top:8px;border-top:1px solid var(--brand-gray);padding-top:8px;" onclick="startCreate()">
|
||||
<span style="flex:1;color:var(--brand-primary);">+ Создать новый инстанс</span>
|
||||
</div>`;
|
||||
document.getElementById('inst-list').innerHTML=html;
|
||||
}
|
||||
|
||||
// Операции
|
||||
html+='<div style="font-size:11px;font-weight:600;padding:4px 8px;color:var(--muted);margin-top:4px;">Операции</div>';
|
||||
d.operations.filter(o=>o.operation!=='reconcile').forEach(o=>{
|
||||
const isCreate=o.operation==='create';
|
||||
const disabled=!isCreate&&!selectedInst;
|
||||
const btn=disabled?'':`<button class="btn btn-primary btn-sm" onclick="selectOperation(${o.svcOperationId},'${o.operation}',${svcId})">Тест</button>`;
|
||||
const hint=isCreate&&svcInstances.length>0?'<span style="font-size:10px;color:var(--muted);">уже есть</span>':
|
||||
(!isCreate&&!selectedInst?'<span style="font-size:10px;color:var(--muted);">выберите инстанс</span>':'');
|
||||
html+=`<div class="op-item${disabled?' disabled':''}" id="op-${o.operation}">${btn} ${o.operation} ${hint}</div>`;
|
||||
});
|
||||
document.getElementById('ops-list').innerHTML=html;
|
||||
}
|
||||
|
||||
function selectInstance(iuid){
|
||||
async function toggleInstance(iuid){
|
||||
selectedInst=iuid;
|
||||
document.querySelectorAll('[data-iuid]').forEach(e=>e.classList.toggle('active',e.dataset.iuid===iuid));
|
||||
document.querySelectorAll('#ops-list .op-item').forEach(e=>{
|
||||
if(e.id!=='op-create') e.classList.remove('disabled');
|
||||
});
|
||||
document.querySelectorAll('#op-create').forEach(e=>e.classList.add('disabled'));
|
||||
|
||||
const opsEl=document.getElementById('ops-'+iuid);
|
||||
if(opsEl.classList.contains('open')){
|
||||
opsEl.classList.remove('open');
|
||||
document.getElementById('params-card').style.display='none';
|
||||
return;
|
||||
}
|
||||
// Закрыть все
|
||||
document.querySelectorAll('.inst-ops').forEach(e=>e.classList.remove('open'));
|
||||
|
||||
const r=await fetch('/api/operations/'+SVC_ID);
|
||||
const d=await r.json();
|
||||
const ops=d.operations.filter(o=>o.operation!=='reconcile'&&o.operation!=='create');
|
||||
opsEl.innerHTML=ops.map(o=>
|
||||
`<div class="inst-item" style="margin-left:0;">
|
||||
<span style="flex:1;">${o.operation}</span>
|
||||
<button class="btn btn-primary btn-sm" onclick="startOperation(${o.svcOperationId},'${o.operation}')">Тест</button>
|
||||
</div>`
|
||||
).join('');
|
||||
opsEl.classList.add('open');
|
||||
}
|
||||
|
||||
async function selectOperation(opId,opName,svcId){
|
||||
selectedOp={opId,opName,svcId};
|
||||
function startCreate(){
|
||||
selectedInst=null;
|
||||
document.querySelectorAll('[data-iuid]').forEach(e=>e.classList.remove('active'));
|
||||
document.querySelectorAll('.inst-ops').forEach(e=>e.classList.remove('open'));
|
||||
loadParams(18,'create'); // svcOperationId for create = 18
|
||||
}
|
||||
|
||||
function startOperation(opId,opName){
|
||||
loadParams(opId,opName);
|
||||
}
|
||||
|
||||
async function loadParams(opId,opName){
|
||||
selectedOp={opId,opName,svcId:SVC_ID};
|
||||
document.getElementById('params-card').style.display='block';
|
||||
document.getElementById('btn-test').style.display='inline-flex';
|
||||
|
||||
@@ -161,8 +172,8 @@ async function selectOperation(opId,opName,svcId){
|
||||
const params=await r.json();
|
||||
const form=document.getElementById('params-form');
|
||||
|
||||
const displayName='autotest-'+svcId+'-'+Date.now().toString(36);
|
||||
form.innerHTML='<div class="param-row"><label class="req">displayName</label><input type="text" id="param-displayname" value="'+displayName+'"></div>'
|
||||
const displayName=opName==='create'?('autotest-1-'+Date.now().toString(36)):'';
|
||||
form.innerHTML=(opName==='create'?`<div class="param-row"><label class="req">displayName</label><input type="text" id="param-displayname" value="${displayName}"></div>`:'')
|
||||
+ params.map(p=>{
|
||||
const req=p.isRequired;
|
||||
const hasDfl=p.defaultValue!==null&&p.defaultValue!==undefined&&p.defaultValue!=='';
|
||||
@@ -179,7 +190,6 @@ async function selectOperation(opId,opName,svcId){
|
||||
}
|
||||
return `<div class="param-row"><label class="${labelCls}">${p.name}</label>${input}</div>`;
|
||||
}).join('');
|
||||
|
||||
document.getElementById('test-status').textContent='';
|
||||
}
|
||||
|
||||
@@ -198,25 +208,19 @@ async function runTest(){
|
||||
params[el.name.replace('p_','')]=v;
|
||||
});
|
||||
|
||||
let instUid=selectedInst||'';
|
||||
if(selectedOp.opName==='create') instUid='';
|
||||
|
||||
try{
|
||||
const r=await fetch('/api/test',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({
|
||||
serviceId:selectedOp.svcId,
|
||||
serviceId:SVC_ID,
|
||||
operation:selectedOp.opName,
|
||||
svcOperationId:selectedOp.opId,
|
||||
params,
|
||||
instanceUid:instUid,
|
||||
displayName:document.getElementById('param-displayname')?.value||('autotest-'+selectedOp.svcId)
|
||||
instanceUid:selectedInst||'',
|
||||
displayName:document.getElementById('param-displayname')?.value||('autotest-1')
|
||||
})});
|
||||
const d=await r.json();
|
||||
const cls=d.status==='OK'?'badge badge-success':'badge';
|
||||
document.getElementById('test-status').innerHTML=` <span class="${cls}">${d.status}</span> ${d.error||''} ${d.duration||''}s`;
|
||||
if(d.status==='OK'&&selectedOp.opName==='create'){
|
||||
// refresh services to update instance state
|
||||
selectService(selectedOp.svcId);
|
||||
}
|
||||
if(d.status==='OK'){await selectService(SVC_ID);}
|
||||
}catch(e){
|
||||
document.getElementById('test-status').textContent=' Ошибка: '+e.message;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user